#!/usr/bin/perl -w

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

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

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

use vars qw(@pci_adapters @chip_ids @superio_ids @undetectable_adapters @dmidecode);

@dmidecode = ( '/usr/local/sbin/dmidecode', '/usr/sbin/dmidecode' );

@undetectable_adapters = ( 'i2c-elektor', 'i2c-elv', 'i2c-philips-par',
                           'i2c-velleman' );

# 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), func (PCI
# Function) and procid (string as appears in /proc/pci; see linux/driver/pci,
# either pci.c or oldproc.c). If no driver is written yet, omit the 
# driver (Driver Name) field. The match (Match Description) field should
# contain a function which returns zero if its two parameter matches
# the text as it would appear in /proc/bus/i2c.
@pci_adapters = ( 
     { 
       vendid => 0x8086,
       devid  => 0x7113,
       func => 3,
       procid => "Intel 82371AB PIIX4 ACPI",
       driver => "i2c-piix4",
       match => sub { $_[0] =~ /^SMBus PIIX4 adapter at / },
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x719b,
       func => 3,
       procid => "Intel 82443MX Mobile",
       driver => "i2c-piix4",
       match => sub { $_[0] =~ /^SMBus PIIX4 adapter at / },
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x2413,
       func => 3,
       procid => "Intel 82801AA ICH",
       driver => "i2c-i801",
       match => sub { $_[0] =~ /^SMBus I801 adapter at [0-9,a-f]{4}/ },
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x2423,
       func => 3,
       procid => "Intel 82801AB ICH0",
       driver => "i2c-i801",
       match => sub { $_[0] =~ /^SMBus I801 adapter at [0-9,a-f]{4}/ },
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x2443,
       func => 3,
       procid => "Intel 82801BA ICH2",
       driver => "i2c-i801",
       match => sub { $_[0] =~ /^SMBus I801 adapter at [0-9,a-f]{4}/ },
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x2483,
       func => 3,
       procid => "Intel 82801CA/CAM ICH3",
       driver => "i2c-i801",
       match => sub { $_[0] =~ /^SMBus I801 adapter at [0-9,a-f]{4}/ },
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x24C3,
       func => 3,
       procid => "Intel 82801DB ICH4",
       driver => "i2c-i801",
       match => sub { $_[0] =~ /^SMBus I801 adapter at [0-9,a-f]{4}/ },
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x24D3,
       func => 3,
       procid => "Intel 82801EB ICH5",
       driver => "i2c-i801",
       match => sub { $_[0] =~ /^SMBus I801 adapter at [0-9,a-f]{4}/ },
     } , 
     { 
       vendid => 0x1106,
       devid  => 0x3040,
       func => 3,
       procid => "VIA Technologies VT82C586B Apollo ACPI",
       driver => "i2c-via",
       match => sub { $_[0] =~ /^VIA i2c/ },
     } ,
     { 
       vendid => 0x1106,
       devid  => 0x3050,
       func => 3,
       procid => "VIA Technologies VT82C596 Apollo ACPI",
       driver => "i2c-viapro",
       match => sub { $_[0] =~ /^SMBus Via Pro adapter at/ },
     } ,
     { 
       vendid => 0x1106,
       devid  => 0x3051,
       func => 3,
       procid => "VIA Technologies VT82C596B ACPI",
       driver => "i2c-viapro",
       match => sub { $_[0] =~ /^SMBus Via Pro adapter at/ },
     } ,
     { 
       vendid => 0x1106,
       devid  => 0x3057,
       func => 4,
       procid => "VIA Technologies VT82C686 Apollo ACPI",
       driver => "i2c-viapro",
       match => sub { $_[0] =~ /^SMBus Via Pro adapter at/ },
     } ,
     { 
       vendid => 0x1106,
       devid  => 0x3074,
       func => 0,
       procid => "VIA Technologies VT8233 VLink South Bridge",
       driver => "i2c-viapro",
       match => sub { $_[0] =~ /^SMBus Via Pro adapter at/ },
     } ,
     { 
       vendid => 0x1106,
       devid  => 0x3147,
       func => 0,
       procid => "VIA Technologies VT8233A South Bridge",
       driver => "i2c-viapro",
       match => sub { $_[0] =~ /^SMBus Via Pro adapter at/ },
     } ,
     { 
       vendid => 0x1106,
       devid  => 0x3177,
       func => 0,
       procid => "VIA Technologies VT8233A/8235 South Bridge",
       driver => "i2c-viapro",
       match => sub { $_[0] =~ /^SMBus Via Pro adapter at/ },
     } ,
     { 
       vendid => 0x1106,
       devid  => 0x8235,
       func => 4,
       procid => "VIA Technologies VT8231 South Bridge",
       driver => "i2c-viapro",
       match => sub { $_[0] =~ /^SMBus Via Pro adapter at/ },
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0008,
       func => 0,
       procid => "Silicon Integrated Systems SIS5595",
       driver => "i2c-sis5595",
       match => sub {  $_[0] =~ /^SMBus SIS5595 adapter at [0-9,a-f]{4}/ },
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0018,
       func => 0,
       procid => "Silicon Integrated Systems 85C503/5513 (LPC Bridge)",
       driver => "i2c-sis645",
       match => sub { $_[0] =~ /^SMBus SiS645 adapter at 0x[0-9,a-f]{4}/ },
     } ,
     {
       vendid => 0x1039,
       devid  => 0x5597,
       func => 0,
       procid => "Silicon Integrated Systems SIS5581/5582/5597/5598 (To be written - Do not use 5595 drivers)",
       driver => "to-be-written",
       match => sub { $_[0] =~ /dontmatchthis/ },
     } ,
     {
       vendid => 0x1039,
       devid  => 0x5598,
       func => 0,
       procid => "Silicon Integrated Systems SIS5598 (To be written - Do not use 5595 drivers)",
       driver => "to-be-written",
       match => sub { $_[0] =~ /dontmatchthis/ },
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0540,
       func => 0,
       procid => "Silicon Integrated Systems SIS540 (To be written - Do not use 5595 drivers)",
       driver => "to-be-written",
       match => sub { $_[0] =~ /dontmatchthis/ },
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0630,
       func => 0,
       procid => "Silicon Integrated Systems SIS630",
       driver => "i2c-sis630",
       match => sub { $_[0] =~ /^SMBus SIS630 adapter at [0-9,a-f]{4}/ },
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0645,
       func => 0,
       procid => "Silicon Integrated Systems SIS645",
       driver => "i2c-sis645",
       match => sub { $_[0] =~ /^SMBus SiS645 adapter at 0x[0-9,a-f]{4}/ },
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0646,
       func => 0,
       procid => "Silicon Integrated Systems SIS645DX",
       driver => "i2c-sis645",
       match => sub { $_[0] =~ /^SMBus SiS645 adapter at 0x[0-9,a-f]{4}/ },
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0648,
       func => 0,
       procid => "Silicon Integrated Systems SIS648",
       driver => "i2c-sis645",
       match => sub { $_[0] =~ /^SMBus SiS645 adapter at 0x[0-9,a-f]{4}/ },
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0650,
       func => 0,
       procid => "Silicon Integrated Systems SIS650",
       driver => "i2c-sis645",
       match => sub { $_[0] =~ /^SMBus SiS645 adapter at 0x[0-9,a-f]{4}/ },
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0651,
       func => 0,
       procid => "Silicon Integrated Systems SIS651",
       driver => "i2c-sis645",
       match => sub { $_[0] =~ /^SMBus SiS645 adapter at 0x[0-9,a-f]{4}/ },
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0735,
       func => 0,
       procid => "Silicon Integrated Systems SIS735",
       driver => "i2c-sis645",
       match => sub { $_[0] =~ /^SMBus SiS645 adapter at 0x[0-9,a-f]{4}/ },
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0745,
       func => 0,
       procid => "Silicon Integrated Systems SIS745",
       driver => "i2c-sis645",
       match => sub { $_[0] =~ /^SMBus SiS645 adapter at 0x[0-9,a-f]{4}/ },
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0746,
       func => 0,
       procid => "Silicon Integrated Systems SIS746",
       driver => "i2c-sis645",
       match => sub { $_[0] =~ /^SMBus SiS645 adapter at 0x[0-9,a-f]{4}/ },
     } ,
     {
       vendid => 0x1039,
       devid  => 0x0730,
       func => 0,
       procid => "Silicon Integrated Systems SIS730",
       driver => "i2c-sis630",
       match => sub { $_[0] =~ /^SMBus SIS630 adapter at [0-9,a-f]{4}/ },
     } ,
#
# Both Ali chips below have same PCI ID. Can't be helped. Only one should load.
#
     {
       vendid => 0x10b9,
       devid => 0x7101,
       func => 0,
       procid => "Acer Labs 1533/1543",
       driver => "i2c-ali15x3",
       match => sub { $_[0] =~ /^SMBus ALI15X3 adapter at/ },
     },
     {
       vendid => 0x10b9,
       devid => 0x7101,
       func => 0,
       procid => "Acer Labs 1535",
       driver => "i2c-ali1535",
       match => sub { $_[0] =~ /^SMBus ALI1535 adapter at/ },
     },
     { 
       vendid => 0x106b,
       devid  => 0x000e,
       func => 0,
       procid => "Apple Computer Inc. Hydra Mac I/O",
       driver => "i2c-hydra",
       match => sub { $_[0] =~ /^Hydra i2c/ },
     },
     { 
       vendid => 0x1022,
       devid  => 0x740b,
       func => 3,
       procid => "AMD-756 Athlon ACPI",
       driver => "i2c-amd756",
       match => sub { $_[0] =~ /^SMBus AMD756 adapter at [0-9,a-f]{4}/ },
     },
     { 
       vendid => 0x1022,
       devid  => 0x7413,
       func => 3,
       procid => "AMD-766 Athlon ACPI",
       driver => "i2c-amd756",
       match => sub { $_[0] =~ /^SMBus AMD766 adapter at [0-9,a-f]{4}/ },
     },
     { 
       vendid => 0x1022,
       devid  => 0x7443,
       func => 3,
       procid => "AMD-768 System Management",
       driver => "i2c-amd756",
       match => sub { $_[0] =~ /^SMBus AMD768 adapter at [0-9,a-f]{4}/ },
     },
     { 
       vendid => 0x1022,
       devid  => 0x746b,
       func => 3,
       procid => "AMD-8111 ACPI",
       driver => "i2c-amd756",
       match => sub { $_[0] =~ /^SMBus AMD8111 adapter at [0-9,a-f]{4}/ },
     },
     { 
       vendid => 0x1022,
       devid  => 0x746a,
       func => 2,
       procid => "AMD-8111 SMBus 2.0",
       driver => "i2c-amd8111",
       match => sub { $_[0] =~ /^SMBus2 AMD8111 adapter at [0-9,a-f]{4}/ },
     },
     {
       vendid => 0x102b,
       devid  => 0x0519,
       func   => 0,
       procid => "MGA 2064W [Millennium]",
       driver => "i2c-matroxfb",
       match  => sub { $_[0] =~ /^DDC:fb[0-9]{1,2}/ },
     },
     {
       vendid => 0x102b,
       devid  => 0x051a,
       func   => 0,
       procid => "MGA 1064SG [Mystique]",
       driver => "i2c-matroxfb",
       match  => sub { $_[0] =~ /^DDC:fb[0-9]{1,2}/ },
     },
     {
       vendid => 0x102b,
       devid  => 0x051b,
       func   => 0,
       procid => "MGA 2164W [Millennium II]",
       driver => "i2c-matroxfb",
       match  => sub { $_[0] =~ /^DDC:fb[0-9]{1,2}/ },
     },
     {
       vendid => 0x102b,
       devid  => 0x051e,
       func   => 0,
       procid => "MGA 1064SG [Mystique] AGP",
       driver => "i2c-matroxfb",
       match  => sub { $_[0] =~ /^DDC:fb[0-9]{1,2}/ },
     },
     {
       vendid => 0x102b,
       devid  => 0x051f,
       func   => 0,
       procid => "MGA 2164W [Millennium II] AGP",
       driver => "i2c-matroxfb",
       match  => sub { $_[0] =~ /^DDC:fb[0-9]{1,2}/ },
     },
     {
       vendid => 0x102b,
       devid  => 0x1000,
       func   => 0,
       procid => "MGA G100 [Productiva]",
       driver => "i2c-matroxfb",
       match  => sub { $_[0] =~ /^DDC:fb[0-9]{1,2}/ },
     },
     {
       vendid => 0x102b,
       devid  => 0x1001,
       func   => 0,
       procid => "MGA G100 [Productiva] AGP",
       driver => "i2c-matroxfb",
       match  => sub { $_[0] =~ /^DDC:fb[0-9]{1,2}/ },
     },
     {
       vendid => 0x102b,
       devid  => 0x0520,
       func   => 0,
       procid => "MGA G200",
       driver => "i2c-matroxfb",
       match  => sub { $_[0] =~ /^DDC:fb[0-9]{1,2}/ },
     },
     {
       vendid => 0x102b,
       devid  => 0x0521,
       func   => 0,
       procid => "MGA G200 AGP",
       driver => "i2c-matroxfb",
       match  => sub { $_[0] =~ /^DDC:fb[0-9]{1,2}/ },
     },
     {
       vendid => 0x102b,
       devid  => 0x0525,
       func   => 0,
       procid => "MGA G400 AGP",
       driver => "i2c-matroxfb",
       match  => sub { $_[0] =~ /^(DDC,MAVEN):fb[0-9]{1,2}/ },
     },
     {
       vendid => 0x121a,
       devid  => 0x0005,
       func   => 0,
       procid => "3Dfx Voodoo3",
       driver => "i2c-voodoo3",
       match  => sub { $_[0] =~ /Banshee adapter/ },
     },
     {
       vendid => 0x121a,
       devid  => 0x0003,
       func   => 0,
       procid => "3Dfx Voodoo Banshee",
       driver => "i2c-voodoo3",
       match  => sub { $_[0] =~ /Banshee adapter/ },
     },
     { 
       vendid => 0x8086,
       devid  => 0x7121,
       func => 0,
       procid => "Intel 82810 GMCH",
       driver => "i2c-i810",
       match => sub { $_[0] =~ /^I810/ },
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x7123,
       func => 0,
       procid => "Intel 82810-DC100 GMCH",
       driver => "i2c-i810",
       match => sub { $_[0] =~ /^I810/ },
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x7125,
       func => 0,
       procid => "Intel 82810E GMCH",
       driver => "i2c-i810",
       match => sub { $_[0] =~ /^I810/ },
     } , 
     { 
       vendid => 0x8086,
       devid  => 0x1132,
       func => 0,
       procid => "Intel 82815 GMCH",
       driver => "i2c-i810",
       match => sub { $_[0] =~ /^I810/ },
     } , 
     { 
       vendid => 0x12d2,
       devid  => 0x0018,
       func => 0,
       procid => "RIVA 128",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x0020,
       func => 0,
       procid => "RIVA TNT",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x0028,
       func => 0,
       procid => "RIVA TNT2",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x0029,
       func => 0,
       procid => "RIVA UTNT2",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x002c,
       func => 0,
       procid => "RIVA VTNT2",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x002d,
       func => 0,
       procid => "RIVA UVTNT2",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x00a0,
       func => 0,
       procid => "RIVA ITNT2",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x0100,
       func => 0,
       procid => "GeForce SDR",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x0101,
       func => 0,
       procid => "GeForce DDR",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x0102,
       func => 0,
       procid => "Quadro",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x0150,
       func => 0,
       procid => "GeForce2 GTS",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x0110,
       func => 0,
       procid => "GeForce2 MX",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x0111,
       func => 0,
       procid => "GeForce2 MX2",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x0113,
       func => 0,
       procid => "Quadro2 MXR",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x0151,
       func => 0,
       procid => "GeForce2 GTS2",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x0152,
       func => 0,
       procid => "GeForce2 Ultra",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x0153,
       func => 0,
       procid => "Quadro2 Pro",
       driver => "i2c-riva",
       match => sub { $_[0] =~ /^NVIDIA display/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x01b4,
       func => 1,
       procid => "nVidia nForce SMBus",
       driver => "i2c-amd756",
       match => sub { $_[0] =~ /^SMBus nVidia nForce adapter at [0-9,a-f]{4}/ },
     } , 
     { 
       vendid => 0x10de,
       devid  => 0x0064,
       func => 1,
       procid => "nVidia Corporation nForce2 SMBus (MCP)",
       driver => "i2c-nforce2",
       match => sub { $_[0] =~ /^SMBus nForce2 adapter at / },
     } , 
     { 
       vendid => 0x1166,
       devid  => 0x0200,
       func => 0,
       procid => "ServerWorks OSB4 South Bridge",
       driver => "i2c-piix4",
       match => sub { $_[0] =~ /^SMBus PIIX4 adapter at / },
     } , 
     { 
       vendid => 0x1055,
       devid  => 0x9463,
       func => 0,
       procid => "SMSC Victory66 South Bridge",
       driver => "i2c-piix4",
       match => sub { $_[0] =~ /^SMBus PIIX4 adapter at / },
     } , 
     { 
       vendid => 0x1166,
       devid  => 0x0201,
       func => 0,
       procid => "ServerWorks CSB5 South Bridge",
       driver => "i2c-piix4",
       match => sub { $_[0] =~ /^SMBus PIIX4 adapter at / },
     } , 
     { 
       vendid => 0x1283,
       devid  => 0x8172,
       func => 0,
       procid => "ITE 8172G MIPS/SH4 Support Chip",
       driver => "i2c-adap-ite",
       match => sub { $_[0] =~ /^ITE IIC adapter/ },
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8A20,
       func => 0,
       procid => "S3 Savage 3D",
       driver => "to-be-written",
       match => sub { $_[0] =~ /^dontmatchthis/ },
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8A21,
       func => 0,
       procid => "S3 Savage 3D MV",
       driver => "to-be-written",
       match => sub { $_[0] =~ /^dontmatchthis/ },
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8A22,
       func => 0,
       procid => "S3 Savage 4",
       driver => "i2c-savage4",
       match => sub { $_[0] =~ /Savage4 adapter/ },
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x9102,
       func => 0,
       procid => "S3 Savage 2000",
       driver => "i2c-savage4",
       match => sub { $_[0] =~ /Savage4 adapter/ },
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8A25,
       func => 0,
       procid => "S3 ProSavage PM",
       driver => "to-be-written",
       match => sub { $_[0] =~ /^dontmatchthis/ },
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8A26,
       func => 0,
       procid => "S3 ProSavage KM",
       driver => "to-be-written",
       match => sub { $_[0] =~ /^dontmatchthis/ },
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8C10,
       func => 0,
       procid => "S3 Savage MX MV",
       driver => "to-be-written",
       match => sub { $_[0] =~ /^dontmatchthis/ },
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8C11,
       func => 0,
       procid => "S3 Savage MX",
       driver => "to-be-written",
       match => sub { $_[0] =~ /^dontmatchthis/ },
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8C12,
       func => 0,
       procid => "S3 Savage IX MV",
       driver => "to-be-written",
       match => sub { $_[0] =~ /^dontmatchthis/ },
     } , 
     { 
       vendid => 0x5333,
       devid  => 0x8C13,
       func => 0,
       procid => "S3 Savage IX",
       driver => "to-be-written",
       match => sub { $_[0] =~ /^dontmatchthis/ },
     } , 
);

use subs qw(mtp008_detect lm78_detect lm78_isa_detect lm78_alias_detect
            lm75_detect lm80_detect w83781d_detect w83781d_alias_detect
            adm1025_detect w83781d_isa_detect gl518sm_detect gl520sm_detect
            adm9240_detect adm1021_detect sis5595_isa_detect eeprom_detect
            via686a_isa_detect adm1022_detect ltc1710_detect gl525sm_detect
            lm87_detect ite_detect ite_isa_detect ite_alias_detect
            ddcmonitor_detect ds1621_detect adm1024_detect fscpos_detect
            fscscy_detect pcf8591_detect arp_detect ipmi_kcs_detect
            ipmi_smic_detect via8231_isa_detect lm85_detect smartbatt_detect
            adm1026_detect w83l785ts_detect);

# 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 something like
#      "Unwritten: <drivername>" if it is not yet available.
#  i2c_addrs (optional): For I2C chips, the range of valid I2C addresses to
#      probe. Recommend avoiding 0x69 because of clock chips.
#  i2c_driver_addrs (optional): For I2C chips, the range of valid I2C
#      addresses probed by the kernel driver. Strictly optional.
#  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_driver_addrs (optional): For ISA chips, the range of valid ISA
#      addresses probed by the kernel driver. Strictly optional.
#  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 LM80",
       driver => "lm80",
       i2c_addrs => [0x28..0x2f],
       i2c_detect => sub { lm80_detect @_} ,
     },
     {
       name => "National Semiconductor LM85",
       driver => "lm85",
       i2c_addrs => [0x2c..0x2e],
       i2c_detect => sub { lm85_detect 0x01, @_},
     },
     {
       name => "Analog Devices ADM1027 or ADT7463",
       driver => "lm85",
       i2c_addrs => [0x2c..0x2e],
       i2c_detect => sub { lm85_detect 0x41, @_},
     },
     {
       name => "SMSC EMC6D100 and EMC6D101",
       driver => "lm85",
       i2c_addrs => [0x2c..0x2e],
       i2c_detect => sub { lm85_detect 0x5c, @_},
     },
     {
       name => "National Semiconductor LM87",
       driver => "lm87",
       i2c_addrs => [0x2c..0x2e],
       i2c_detect => sub { lm87_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 => [0x20..0x2f], 
       i2c_detect => sub { w83781d_detect 2, @_},
     } ,
     {
       name => "Winbond W83791D",
       driver => "w83781d",
       i2c_addrs => [0x20..0x2f], 
       i2c_detect => sub { w83781d_detect 7, @_},
     } ,
     {
       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 => "Asus AS99127F",
       driver => "w83781d",
       i2c_addrs => [0x20..0x2f], 
       i2c_detect => sub { w83781d_detect 4, @_},
     } ,
     {
       name => "Winbond W83L784R/AR",
       driver => "to-be-written",
       i2c_addrs => [0x20..0x2f], 
       i2c_detect => sub { w83781d_detect 6, @_},
     } ,
     {
       name => "Winbond W83L785TS",
       driver => "to-be-written",
       i2c_addrs => [0x2e], 
       i2c_detect => sub { w83l785ts_detect 0, @_},
     } ,
     {
       name => "Winbond W83697HF",
       driver => "w83781d",
       isa_addrs => [0x290],
       isa_detect => sub { w83781d_isa_detect 5, @_ },
     } ,
     {
       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 => "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 => "Maxim MAX1617",
       driver => "adm1021",
       i2c_addrs => [0x18..0x1a,0x29..0x2b,0x4c..0x4e],
       i2c_detect => sub { adm1021_detect 1, @_ },
     },
     {
       name => "Maxim MAX1617A",
       driver => "adm1021",
       i2c_addrs => [0x18..0x1a,0x29..0x2b,0x4c..0x4e],
       i2c_detect => sub { adm1021_detect 2, @_ },
     },
     {
       name => "TI THMC10",
       driver => "adm1021",
       i2c_addrs => [0x18..0x1a,0x29..0x2b,0x4c..0x4e],
       i2c_detect => sub { adm1021_detect 3, @_ },
     },
     {
       name => "National Semiconductor LM84",
       driver => "adm1021",
       i2c_addrs => [0x18..0x1a,0x29..0x2b,0x4c..0x4e],
       i2c_detect => sub { adm1021_detect 4, @_ },
     },
     {
       name => "Genesys Logic GL523SM",
       driver => "adm1021",
       i2c_addrs => [0x18..0x1a,0x29..0x2b,0x4c..0x4e],
       i2c_detect => sub { adm1021_detect 5, @_ },
     },
     {
       name => "Onsemi MC1066",
       driver => "adm1021",
       i2c_addrs => [0x18..0x1a,0x29..0x2b,0x4c..0x4e],
       i2c_detect => sub { adm1021_detect 6, @_ },
     },
     {
       name => "Analog Devices ADM1022",
       driver => "thmc50",
       i2c_addrs => [0x2c..0x2f],
       i2c_detect => sub { adm1022_detect 0, @_ },
     },
     {
       name => "Texas Instruments THMC50",
       driver => "thmc50",
       i2c_addrs => [0x2c..0x2f],
       i2c_detect => sub { adm1022_detect 1, @_ },
     },
     {
       name => "Silicon Integrated Systems SIS5595",
       driver => "sis5595",
       isa_addrs => [ 0 ],
       isa_detect => sub { sis5595_isa_detect @_ },
     },
     {
       name => "VIA Technologies VT82C686 Integrated Sensors",
       driver => "via686a",
       isa_addrs => [ 0 ],
       isa_detect => sub { via686a_isa_detect @_ },
     },
     {
       name => "VIA Technologies VT8231 Integrated Sensors",
       driver => "vt8231",
       isa_addrs => [ 0 ],
       isa_detect => sub { via8231_isa_detect @_ },
     },
     {
       name => "ITE IT8705F / IT8712F / SiS 950",
       driver => "it87",
       i2c_addrs => [0x20..0x2f],
       i2c_detect => sub { ite_detect 0, @_ },
       isa_addrs => [0x290],
       isa_detect => sub { ite_isa_detect 0, @_ },
     } ,
     {
       name => "Serial EEPROM",
       driver => "eeprom",
       i2c_addrs => [0x50..0x57],
       i2c_detect => sub { eeprom_detect @_ },
     },
     {
       name => "LTC1710",
       driver => "ltc1710",
       i2c_addrs => [0x58..0x5a],
       i2c_detect => sub { ltc1710_detect @_ },
     },
     {
       name => "DDC monitor",
       driver => "ddcmon",
       i2c_addrs => [0x50],
       i2c_detect => sub { ddcmonitor_detect @_ },
     },
     {
       name => "FSC poseidon chip",
       driver => "fscpos",
       i2c_addrs => [0x73],
       i2c_detect => sub { fscpos_detect @_ },
     },
     {
       name => "FSC Scylla chip",
       driver => "fscscy",
       i2c_addrs => [0x73],
       i2c_detect => sub { fscscy_detect @_ },
     },
     {
       name => "Philips Semiconductors PCF8591",
       driver => "pcf8591",
       i2c_addrs => [0x48..0x4f],
       i2c_detect => sub { pcf8591_detect @_},
     },
     {
       name => "SMBus 2.0 ARP-Capable Device",
       driver => "smbus-arp",
       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",
       driver => "smartbatt",
       i2c_addrs => [0x0b],
       i2c_detect => sub { smartbatt_detect @_},
     },
);

# This is a list of all recognized superio chips. 
# Each entry must have the following fields:
#  name: The full chip name
#  driver: The driver name (without .o extension). Put in something like
#      "Unwritten: <drivername>" if it is not yet available.
#  addrreg: The address register
#  datareg: The data register
#  enter: The password sequence to write to the address register
#  devidreg: The device ID register(s)
#  devid: The device ID(s) we have to match (base device)
#  logdevreg: The logical device register
#  logdev: The logical device containing the sensors
#  actreg (optional): The activation register within the logical device
#  actmask (optional): The activation bit in the activation register
#  basereg: The I/O base register within the logical device
#  exitreg: The register to write the exit value to
#  exit: The value to write to the exit register to exit
#  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.
@superio_ids = (
#     {
#	name => "ITE 8705 Super IO Sensors",
#	driver => "it87",
#	addrreg => 0x2e,
#	datareg => 0x2f,
#	enter => [0x55, 0x01, 0x55, 0x55].
# code doesn't handle multiple devid regs yet
#	devidreg => [0x20, 0x21],
#	devid => [0x87, 0x05],
#	logdevreg => 0x07,
#	logdev => 0x04,
#	actreg => 0x30,
#	actmask => 0x01,
#	basereg => 0x60,
# exit is writing 0x01 to reg 0x02. - not compatible with other superio chips
#	exitreg => 0x02,
#	exit => 0x01,
#     }, 
# Nat'l untested
#     {
#	name => "Nat. Semi. PC87366 Super I/O Sensors",
#	driver => "xxxx",
#	addrreg => 0x2e,
#	datareg => 0x2f,
#	enter => ?? none required?
#	devidreg => 0x20,
#	devid => 0xe9,
#	logdevreg => 0x07,
#	logdev => 0x09 # fans; temps at 0x0e
#	actreg => 0x30,
#	actmask => 0x01,
#	basereg => 0x60,
# exit is writing 0x01 to reg 0x02. - not compatible with other superio chips
#	exitreg => ?? none req'd??
#	exit => ??
#     }, 
     {
	name => "SMSC 47M1xx Super IO Fan Sensors",
	driver => "smsc47m1",
	addrreg => 0x2e,
	exitreg => 0x2e,
	datareg => 0x2f,
	enter => [0x55],
	devidreg => 0x20,
	devid => 0x59,
	logdevreg => 0x07,
	logdev => 0x0a,
	actreg => 0x20,
	actmask => 0x01,
	basereg => 0x60,
	exit => 0xaa,
     }, 
     {
	name => "VT1211 Super IO Sensors",
	driver => "vt1211",
	addrreg => 0x2e,
	exitreg => 0x2e,
	datareg => 0x2f,
	enter => [0x87, 0x87],
	devidreg => 0x20,
	devid => 0x3c,
	logdevreg => 0x07,
	logdev => 0x0b,
	actreg => 0x30,
	actmask => 0x01,
	basereg => 0x60,
	exit => 0xaa,
     }, 
     {
	name => "Winbond W83627HF Super IO Sensors",
	driver => "w83627hf",
	addrreg => 0x2e,
	exitreg => 0x2e,
	datareg => 0x2f,
	enter => [0x87, 0x87],
	devidreg => 0x20,
	devid => 0x52,
	logdevreg => 0x07,
	logdev => 0x0b,
	actreg => 0x30,
	actmask => 0x01,
	basereg => 0x60,
	exit => 0xaa,
     }, 
     {
	name => "Winbond W83627THF Super IO Sensors",
	driver => "w83627hf",
	addrreg => 0x2e,
	exitreg => 0x2e,
	datareg => 0x2f,
	enter => [0x87, 0x87],
	devidreg => 0x20,
	devid => 0x82,
	logdevreg => 0x07,
	logdev => 0x0b,
	actreg => 0x30,
	actmask => 0x01,
	basereg => 0x60,
	exit => 0xaa,
     }, 
     {
	name => "Winbond W83697HF Super IO Sensors",
	driver => "w83627hf",
	addrreg => 0x2e,
	exitreg => 0x2e,
	datareg => 0x2f,
	enter => [0x87, 0x87],
	devidreg => 0x20,
	devid => 0x60,
	logdevreg => 0x07,
	logdev => 0x0b,
	actreg => 0x30,
	actmask => 0x01,
	basereg => 0x60,
	exit => 0xaa,
     }, 
);

#######################
# 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.
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|O_BINARY)
    or die "/dev/port: $!\n";
  binmode (IOPORTS, ':raw');
}

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

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

use vars qw(@modules_list);

sub initialize_modules_list
{
  open INPUTFILE, "/proc/modules" or die "Can't access /proc/modules!";
  while (<INPUTFILE>) {
    push @modules_list, /^(\S*)/ ;
  }
  close INPUTFILE;
}

##############
# PCI ACCESS #
##############

use vars qw(@pci_list);

# This function returns a list of hashes. Each hash has some PCI information 
# (more than we will ever need, probably). The most important
# fields are 'bus', 'slot', 'func' (they uniquely identify a PCI device in 
# a computer) and 'vendid','devid' (they uniquely identify a type of device).
# /proc/bus/pci/devices is only available on late 2.1 and 2.2 kernels.
sub read_proc_dev_pci
{
  my ($dfn,$vend,@pci_list);
  open INPUTFILE, "/proc/bus/pci/devices" or return;
  while (<INPUTFILE>) {
    my $record = {};
    ($dfn,$vend,$record->{irq},$record->{base_addr0},$record->{base_addr1},
          $record->{base_addr2},$record->{base_addr3},$record->{base_addr4},
          $record->{base_addr5},$record->{rom_base_addr}) = 
          map { oct "0x$_" } (split) [0..9];
    $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;
  }
  close INPUTFILE or return;
  return @pci_list;
}

# This function returns a list of hashes. Each hash has some PCI 
# information. The important fields here are 'bus', 'slot', 'func' (they
# uniquely identify a PCI device in a computer) and 'desc' (a functional
# description of the PCI device). If this is an 'unknown device', the
# vendid and devid fields are set instead.
sub read_proc_pci
{
  my @pci_list;
  open INPUTFILE, "/proc/pci" or return;
  while (<INPUTFILE>) {
    my $record = {};
    if (($record->{bus},$record->{slot},$record->{func}) = 
        /^\s*Bus\s*(\S)+\s*,\s*device\s*(\S+)\s*,\s*function\s*(\S+)\s*:\s*$/) {
      my $desc = <INPUTFILE>;
      $_ = <INPUTFILE>;
      if (($desc =~ /Unknown device/) and
              (($record->{vendid},$record->{devid}) = 
                         /^\s*Vendor id=(\S+)\.\s*Device id=(\S+)\.$/)) {
        $record->{vendid} = hex $record->{vendid};
        $record->{devid} = hex $record->{devid};
      } else {
        $record->{desc} = $desc;
      }
      push @pci_list,$record;
    }
  }
  close INPUTFILE or return;
  return @pci_list;
}

sub initialize_proc_pci
{
  @pci_list = read_proc_dev_pci;
  @pci_list = read_proc_pci     if not defined @pci_list;
  die "Can't access either /proc/bus/pci/ or /proc/pci!" 
                                    if not defined @pci_list;
}

#####################
# ADAPTER DETECTION #
#####################

sub all_available_adapters
{
  my @res = ();
  my ($module,$adapter);
  MODULES:
  foreach $module (@modules_list) {
    foreach $adapter (@pci_adapters) {
      if (exists $adapter->{driver} and $module eq $adapter->{driver}) {
        push @res, $module;
        next MODULES;
      }
    }
  }
  return @res;
}

sub adapter_pci_detection
{
  my ($device,$try,@res);
  print "Probing for PCI bus adapters...\n";

  foreach $device (@pci_list) {
    foreach $try (@pci_adapters) {
      if ((defined($device->{vendid}) and 
           $try->{vendid} == $device->{vendid} and
           $try->{devid} == $device->{devid} and
           $try->{func} == $device->{func}) or
          (! defined($device->{vendid}) and
           $device->{desc} =~ /$try->{procid}/ and
           $try->{func} == $device->{func})) {
        printf "Use driver `%s' for device %02x:%02x.%x: %s\n",
               $try->{driver}?$try->{driver}:"<To Be Written>",
               $device->{bus},$device->{slot},$device->{func},$try->{procid};
        push @res,$try->{driver};
      }
    }
  }
  if (! @res) {
    print ("Sorry, no PCI bus adapters found.\n");
  } else {
    printf ("Probe succesfully concluded.\n");
  }
  return @res;
}

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

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

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

# To do: support i2c-level access (through sysread/syswrite, probably).
# I can't test this at all (PIIX4 does not support this), so I have not
# included it.

use vars qw($IOCTL_I2C_RETRIES $IOCTL_I2C_TIMEOUT $IOCTL_I2C_UDELAY 
            $IOCTL_I2C_MDELAY $IOCTL_I2C_SLAVE $IOCTL_I2C_TENBIT 
            $IOCTL_I2C_SMBUS);

# These are copied from <linux/i2c.h> and <linux/smbus.h>

# For bit-adapters:
$IOCTL_I2C_RETRIES = 0x0701;
$IOCTL_I2C_TIMEOUT = 0x0702;
$IOCTL_I2C_UDELAY = 0x0705;
$IOCTL_I2C_MDELAY = 0x0706;

# General ones:
$IOCTL_I2C_SLAVE = 0x0703;
$IOCTL_I2C_TENBIT = 0x0704;
$IOCTL_I2C_SMBUS = 0x0720;



use vars qw($SMBUS_READ $SMBUS_WRITE $SMBUS_QUICK $SMBUS_BYTE $SMBUS_BYTE_DATA 
            $SMBUS_WORD_DATA $SMBUS_PROC_CALL $SMBUS_BLOCK_DATA);

# These are copied from <linux/smbus.h>

$SMBUS_READ = 1;
$SMBUS_WRITE = 0;
$SMBUS_QUICK = 0;
$SMBUS_BYTE = 1;
$SMBUS_BYTE_DATA  = 2;
$SMBUS_WORD_DATA  = 3;
$SMBUS_PROC_CALL = 4;
$SMBUS_BLOCK_DATA = 5;

# 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]: Byte to write
# Returns: -1 on failure, 0 on success.
sub i2c_smbus_write_byte
{
  my ($file,$command) = @_;
  my $data = [$command];
  i2c_smbus_access $file, $SMBUS_WRITE, 0, $SMBUS_BYTE, $data 
         or return -1;
  return 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)
# $_[2]: Byte to write
# Returns: -1 on failure, 0 on success.
sub i2c_smbus_write_byte_data
{
  my ($file,$command,$value) = @_;
  my $data = [$value];
  i2c_smbus_access $file, $SMBUS_WRITE, $command, $SMBUS_BYTE_DATA, $data 
         or return -1;
  return 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]: Command byte (usually register number)
# $_[2]: Byte to write
# Returns: -1 on failure, 0 on success.
# Note: some devices use the wrong endiannes; use swap_bytes to correct for 
# this.
sub i2c_smbus_write_word_data
{
  my ($file,$command,$value) = @_;
  my $data = [$value & 0xff, $value >> 8];
  i2c_smbus_access $file, $SMBUS_WRITE, $command, $SMBUS_WORD_DATA, $data 
         or return -1;
  return 0;
}

# $_[0]: Reference to an opened filehandle
# $_[1]: Command byte (usually register number)
# $_[2]: Word to write
# Returns: -1 on failure, read word on success.
# Note: some devices use the wrong endiannes; use swap_bytes to correct for 
# this.
sub i2c_smbus_process_call
{
  my ($file,$command,$value) = @_;
  my $data = [$value & 0xff, $value >> 8];
  i2c_smbus_access $file, $SMBUS_WRITE, $command, $SMBUS_PROC_CALL, $data 
         or return -1;
  return $$data[0] + 256 * $$data[1];
}

# $_[0]: Reference to an opened filehandle
# $_[1]: Command byte (usually register number)
# Returns: Undefined on failure, a list of read bytes on success
# Note: some devices use the wrong endiannes; use swap_bytes to correct for 
# this.
sub i2c_smbus_read_block_data
{
  my ($file,$command) = @_;
  my $data = [];
  i2c_smbus_access $file, $SMBUS_READ, $command, $SMBUS_BLOCK_DATA, $data 
         or return;
  shift @$data;
  return @$data;
}

# $_[0]: Reference to an opened filehandle
# $_[1]: Command byte (usually register number)
# @_[2..]: List of values to write
# Returns: -1 on failure, 0 on success.
# Note: some devices use the wrong endiannes; use swap_bytes to correct for 
# this.
sub i2c_smbus_write_block_data
{
  my ($file,$command,@data) = @_;
  i2c_smbus_access $file, $SMBUS_WRITE, $command, $SMBUS_BLOCK_DATA, \@data 
         or return;
  return 0;
}

####################
# 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_algo' containing an algorithm 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 'i2c_extra' if this is an I2C detection and the address
#       is not normally probed by the kernel driver
#  with field 'isa_addr' containing the ISA address this chip is on 
#       (if this is an ISA detection)
#  with field 'isa_extra' if this is an ISA detection and the address
#       is not normally probed by the kernel driver
#  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);

  # 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;
  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;
        }
        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.
    @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];
          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;
  }
}

# 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 FILE,"/dev/i2c-$new_misdetected_ref->[$i]->{i2c_devnr}" or
        open FILE,"/dev/i2c$new_misdetected_ref->[$i]->{i2c_devnr}" or
          open FILE,"/dev/i2c/$new_misdetected_ref->[$i]->{i2c_devnr}" or
             print("Can't open ",
                   "/dev/i2c[-/]$new_misdetected_ref->[$i]->{i2c_devnr}?!?\n"),
             next;
      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};
        $new_misdetected_ref->[$i]->{isa_extra} = $datahash->{isa_extra} 
               if exists $datahash->{isa_extra};
        close FILE;
        return $new_misdetected_ref->[$i]; 
      }
      close FILE;
    }
  }

  # 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 FILE,"/dev/i2c-$new_detected_ref->[$i]->{i2c_devnr}" or
        open FILE,"/dev/i2c$new_detected_ref->[$i]->{i2c_devnr}" or
          open FILE,"/dev/i2c/$new_detected_ref->[$i]->{i2c_devnr}" or
             print("Can't open ",
                   "/dev/i2c[-/]$new_detected_ref->[$i]->{i2c_devnr}?!?\n"),
             next;
      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};
        $new_detected_ref->[$i]->{isa_extra} = $datahash->{isa_extra} 
               if exists $datahash->{isa_extra};
        ($datahash) = splice (@$new_detected_ref, $i, 1);
        close FILE;
        $isalias=1;
        last;
      }
      close FILE;
    }
  }


  # Find out whether our new entry should go into the detected or the
  # misdetected list. We only compare main isa_addr here, of course.
  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;
        } else {
          push @$misdetected_ref,$detected_ref->[$i];
          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 name of the algorithm, as appearing in /proc/bus/i2c
# $_[3]: The driver of the adapter
# @_[4..]: Addresses not to scan
sub scan_adapter
{
  my ( $adapter_nr,$adapter_name,$algorithm_name,$adapter_driver, 
       $not_to_scan) = @_;
  my ($chip, $addr, $conf,@chips,$new_hash,$other_addr);

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

  open FILE,"/dev/i2c-$adapter_nr" or 
    open FILE,"/dev/i2c$adapter_nr" or 
      open FILE,"/dev/i2c/$adapter_nr" or 
         (print "Can't open /dev/i2c[-/]$adapter_nr\n"), return;

  # Now scan each address in turn
  foreach $addr (0..0x7f) {
    # 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;
    }

    i2c_set_slave_addr(\*FILE,$addr) or 
        printf("Client at address 0x%02x can not be probed - unload all client drivers first!\n",$addr), next;

    next unless i2c_smbus_write_quick(\*FILE,$SMBUS_WRITE) >= 0;
    printf "Client found at address 0x%02x\n",$addr;
    # Prevent 24RF08 corruption
    if($addr >= 0x54 and $addr <= 0x57) {
        i2c_smbus_write_quick(\*FILE,$SMBUS_WRITE);
    }

    foreach $chip (@chip_ids) {
      if (exists $$chip{i2c_addrs} and contains $addr, @{$$chip{i2c_addrs}}) {
        print "Probing for `$$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_algo => $algorithm_name,
                        i2c_driver => $adapter_driver,
                        i2c_devnr => $adapter_nr,
                      };
          if (@chips) {
            my @chips_copy = @chips;
            $new_hash->{i2c_sub_addrs} = \@chips_copy;
          }
          $new_hash->{i2c_extra} = 0 
                 if exists $chip->{i2c_driver_addrs} and
                    not contains( $addr , @{$chip->{i2c_driver_addrs}});
          add_i2c_to_chips_detected $$chip{driver}, $new_hash;
        } else {
          print "Failed!\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};
    print "Probing for `$$chip{name}'\n";
    foreach $addr (@{$$chip{isa_addrs}}) {
      if ($addr) {
        printf "  Trying address 0x%04x... ", $addr;
      } else {
        print "  Trying general detect... ";
      }
      $conf = &{$$chip{isa_detect}} ($addr);
      print("Failed!\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->{isa_extra} = 0 
             if exists $chip->{isa_driver_addrs} and
                not contains ($addr, @{$chip->{isa_driver_addrs}});
      $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};
      }
    }
  }
}

sub scan_superio
{
  my ($chip,$val,$addr,$conf);
  foreach $chip (@superio_ids) {
    print "Probing for `$$chip{name}'\n";
# write the password
    foreach $val (@{$$chip{enter}}) {
      outb($$chip{addrreg}, $val);
    }
# check the device ID
    outb($$chip{addrreg}, $$chip{devidreg});
    $val = inb($$chip{datareg});
    if($val == $$chip{devid}) {
      print "  Success...";
# switch to the sensor logical device
      outb($$chip{addrreg}, $$chip{logdevreg});
      outb($$chip{datareg}, $$chip{logdev});
# check the activation register
      if(exists $$chip{actreg}) {
	outb($$chip{addrreg}, $$chip{actreg});
	$val = inb($$chip{datareg});
	if(!($val & $$chip{actmask})) {
	  print " but not activated, module may not find\n";
	  outb($$chip{exitreg}, $$chip{exit});
	  next;
	}
      }
# Get the IO base register
      outb($$chip{addrreg}, $$chip{basereg});
      $addr = inb($$chip{datareg});
      outb($$chip{addrreg}, $$chip{basereg} + 1);
      $addr = ($addr << 8) | inb($$chip{datareg});
      if($addr == 0) {
	print " but not activated, module may not find\n";
	outb($$chip{addrreg}, $$chip{exit});
	next;
      }	        
      printf " found at address 0x%04x\n", $addr;
      my $new_hash = { conf => 8,
                       isa_addr => $addr,
                       chipname =>  $$chip{name}
                     };
      add_isa_to_chips_detected $$chip{alias_detect},$$chip{driver},
                                            $new_hash;
    } else {
      if($val == 0xff) {	
        print "  Failed!\n";
      } else {
        printf "  Failed! (0x%02x)\n", $val;
      }
    }
    outb($$chip{exitreg}, $$chip{exit});
  }
}


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

# 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, (7) 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 (7);
}

# $_[0]: Chip to detect (0 = LM78, 1 = LM78-J, 2 = LM79)
# $_[1]: Address
# Returns: undef if not detected, 7 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);
  return 7;
}


# $_[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 may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: undef if not detected, (3) if detected.
# Registers used:
#   0x01: Configuration
#   0x02: Hysteresis
#   0x03: Overtemperature Shutdown
# Detection really sucks! It is only based on the fact that the LM75 has only
# four registers. Any other chip in the valid address range with only four
# registers will be detected too.
# Note that register $00 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 $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);
  for ($i = 0x00; $i <= 0x1f; $i += 1) {
    return if i2c_smbus_read_byte_data($file,($i * 0x08) + 0x01) != $conf;
    return if i2c_smbus_read_word_data($file,($i * 0x08) + 0x02) != $hyst;
    return if i2c_smbus_read_word_data($file,($i * 0x08) + 0x03) != $os;
  }
  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: undef if not detected, (3) if detected,
#   (6) or (9) if even more bits match. 
# Registers used:
#   0xAC: Configuration
# Detection is weak. We check if Bit 3 is set and Bit 2 is clear.
# The DS1621 will aways have a config like 0x????10??. A even better
# match would be 0x0??01000.
sub ds1621_detect
{
  my $i;
  my ($file,$addr) = @_;
  my $conf = i2c_smbus_read_byte_data($file,0xAC);
  return (9) if ($conf & 0x9F) == 0x98;
  return (6) if ($conf & 0x0F) == 0x08;
  return (3) if ($conf & 0x0C) == 0x08;
  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, (3) if detected.
# Registers used:
# Registers used:
#   0x02: Interrupt state register
# How to detect this beast? 
sub lm80_detect
{
  my $i;
  my ($file,$addr) = @_;
  return if (i2c_smbus_read_byte_data($file,0x02) & 0xc0) != 0;
  for ($i = 0x2a; $i <= 0x3d; $i++) {
    my $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;
  }
  return (3);
}

# $_[0]: Vendor to check for
#   (0x01 = National Semi, 0x41 = Analog Dev)
# $_[1]: A reference to the file descriptor to access this chip.
# #_[2]: Base address.
# Returns: undef if not detected, (7) if detected.
# Registers used: 0x3e == Vendor register.
#		  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;
  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, 6 = W83L784R/AR
#                        7 = W83791D
# $_[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); ignore LSB.
# Note: Fails if the W8378xD is not in bank 0!
# Note: Detection overrules a previous LM78 detection
# Note: AS99127F address register 0x48 not supported?
sub w83781d_detect
{
  my ($reg1,$reg2,@res);
  my ($chip,$file,$addr) = @_;
  return unless (i2c_smbus_read_byte_data($file,0x48) == $addr)
    or ($chip == 4) or ($chip == 6);
  $reg1 = i2c_smbus_read_byte_data($file,0x4e);
  $reg2 = i2c_smbus_read_byte_data($file,0x4f);
  if ($chip != 4) {
    return unless (($reg1 & 0x80) == 0x00 and $reg2 == 0xa3) or 
                  (($reg1 & 0x80) == 0x80 and $reg2 == 0x5c);
  }
  if ($chip == 4) {
    return unless (($reg1 & 0x80) == 0x00 and
			($reg2 == 0xc3 or $reg2 == 0x94)) or 
                  (($reg1 & 0x80) == 0x80 and
			($reg2 == 0x12 or $reg2 == 0x06));
  }
  return unless ($reg1 & 0x07) == 0x00;
  $reg1 = i2c_smbus_read_byte_data($file,0x58) & 0xfe;
  return if $chip == 0 and  $reg1 != 0x10;
  return if $chip == 1 and  $reg1 != 0x30;
  return if $chip == 2 and  $reg1 != 0x40;
  return if $chip == 3 and  $reg1 != 0x20;
  return if $chip == 4 and  $reg1 != 0x30;
  return if $chip == 6 and  $reg1 != 0x50;
  return if $chip == 7 and  $reg1 != 0x70;
  $reg1 = i2c_smbus_read_byte_data($file,0x4a);
  @res = (8);
  push @res, ($reg1 & 0x07) + 0x48 unless $reg1 & 0x08 ;
  push @res, (($reg1 & 0x80) >> 4) + 0x48 unless ($reg1 & 0x80 or $chip == 2);
  return @res;
}

# $_[0]: Chip to detect (0 = W83781D, 1 = W83782D, 2 = W83783S, 3 = W83627HF)
# $_[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, 5 = W83697HF)
#        (W83783S and AS99127F not on ISA bus)
# $_[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) & 0xfe;
  return if $chip == 0 and  $reg1 != 0x10;
  return if $chip == 1 and  $reg1 != 0x30;
  return if $chip == 3 and  $reg1 != 0x20;
  return if $chip == 5 and  $reg1 != 0x60;
  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)
# $_[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);
  return unless (i2c_smbus_read_byte_data($file,0x40) & 0x80) == 0x00;
  return unless (i2c_smbus_read_byte_data($file,0x3f) & 0xc0) == 0xc0;
  return (8);
}

# $_[0]: Chip to detect (0 = ADM1025)
# $_[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 adm1025_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) == 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 = MAX1617, 2 = MAX1617A, 3 = THMC10, 4 = LM84, 5 = GL523, 6 = 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, (6) or (3) if detected.
# Registers used:
#   0x04: Company ID (LM84 only)
#   0xfe: Company ID
#   0xff: Revision (Maxim only)
#   0x02: Status
# Note: Especially the Maxim has very bad detection; we give it a low 
# confidence value.
sub adm1021_detect
{
  my $reg;
  my ($chip, $file,$addr) = @_;
  return if $chip == 0 and i2c_smbus_read_byte_data($file,0xfe) != 0x41;
  return if $chip == 3 and i2c_smbus_read_byte_data($file,0xfe) != 0x49;
  return if $chip == 4 and i2c_smbus_read_byte_data($file,0x04) != 0x00;
  return if $chip == 5 and i2c_smbus_read_byte_data($file,0xfe) != 0x23;
  return if $chip == 2 and i2c_smbus_read_byte_data($file,0xfe) != 0x4d and
                           i2c_smbus_read_byte_data($file,0xff) != 0x01;
  return if $chip == 6 and i2c_smbus_read_byte_data($file,0xfe) != 0x54;
  # The remaining things are flaky at best. Perhaps something can be done
  # with the fact that some registers are unreadable?
  return if (i2c_smbus_read_byte_data($file,0x02) & 0x03) != 0;
  if ($chip == 1) {
    return (3);
  } else {
    return (6);
  }
}

# $_[0]: Address
# Returns: undef if not detected, (9) if detected.
# Note: It is already 99% certain this chip exists if we find the PCI
# entry. The exact address is encoded in PCI space.
sub sis5595_isa_detect
{
  my ($addr) = @_;
  my ($adapter,$try,$local_try);
  my $found = 0;
  foreach $local_try (@pci_adapters) {
    if ($local_try->{procid} eq "Silicon Integrated Systems SIS5595") {
      $try = $local_try;
      $found = 1;
      last;
    }
  }
  return if not $found;

  $found = 0;
  foreach $adapter (@pci_list) {
    if ((defined($adapter->{vendid}) and 
         $try->{vendid} == $adapter->{vendid} and
         $try->{devid} == $adapter->{devid} and
         $try->{func} == $adapter->{func}) or
        (! defined($adapter->{vendid}) and
         $adapter->{desc} =~ /$try->{procid}/ and
         $try->{func} == $adapter->{func})) {
      $found = 1;
      last;
    }
  }
  return if not $found;

  return 9;
}

# $_[0]: Address
# Returns: undef if not detected, (9) if detected.
# Note: It is already 99% certain this chip exists if we find the PCI
# entry. The exact address is encoded in PCI space.
sub via686a_isa_detect
{
  my ($addr) = @_;
  my ($adapter,$try,$local_try);
  my $found = 0;
  foreach $local_try (@pci_adapters) {
    if ($local_try->{procid} eq "VIA Technologies VT82C686 Apollo ACPI") {
      $try = $local_try;
      $found = 1;
      last;
    }
  }
  return if not $found;

  $found = 0;
  foreach $adapter (@pci_list) {
    if ((defined($adapter->{vendid}) and 
         $try->{vendid} == $adapter->{vendid} and
         $try->{devid} == $adapter->{devid} and
         $try->{func} == $adapter->{func}) or
        (! defined($adapter->{vendid}) and
         $adapter->{desc} =~ /$try->{procid}/ and
         $try->{func} == $adapter->{func})) {
      $found = 1;
      last;
    }
  }
  return if not $found;

  return 9;
}

# $_[0]: Address
# Returns: undef if not detected, (9) if detected.
# Note: It is already 99% certain this chip exists if we find the PCI
# entry. The exact address is encoded in PCI space.
sub via8231_isa_detect
{
  my ($addr) = @_;
  my ($adapter,$try,$local_try);
  my $found = 0;
  foreach $local_try (@pci_adapters) {
    if ($local_try->{procid} eq "VIA Technologies VT8231 South Bridge") {
      $try = $local_try;
      $found = 1;
      last;
    }
  }
  return if not $found;

  $found = 0;
  foreach $adapter (@pci_list) {
    if ((defined($adapter->{vendid}) and 
         $try->{vendid} == $adapter->{vendid} and
         $try->{devid} == $adapter->{devid} and
         $try->{func} == $adapter->{func}) or
        (! defined($adapter->{vendid}) and
         $adapter->{desc} =~ /$try->{procid}/ and
         $try->{func} == $adapter->{func})) {
      $found = 1;
      last;
    }
  }
  return if not $found;

  return 9;
}

# $_[0]: Chip to detect (0 = ..., 1 = ...)
# $_[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 (tops LM78).
# Registers used:
#   0x00: Configuration
#   0x48: Full I2C Address
#   0x58: Mfr ID
# 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) & 0x80) == 0x00;
  return unless i2c_smbus_read_byte_data($file,0x58) == 0x90;
  return (8);
}

# $_[0]: Chip to detect (0 = ..., 1 = ...)
# $_[1]: Address
# Returns: undef if not detected, 8 if detected (tops LM78).
# Note: Only address 0x290 is scanned at this moment.
sub ite_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(0x00) & 0x80) == 0x00;
  my $reg = &$readproc(0x58);
  return unless ($reg == 0x90);
  return 8;
}


# $_[0]: Chip to detect (0 = ..., 1 = ...)
# $_[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 = 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 may assume an i2c_set_slave_addr was already done.
# $_[1]: Address
# Returns: 8 for a memory eeprom, 6 or 8 for a Sony Vaio eeprom,
#          1 or 2 for an unknown eeprom
# Registers used:
#   0-63: PC-100 Data and Checksum
#   2-5, 128-131: Sony Vaio Data
sub eeprom_detect
{
  my ($file,$addr) = @_;
  # Check the checksum for validity (only works for PC-100 DIMMs)
  my $checksum = 0;
  for (my $i = 0; $i <= 62; $i ++) {
    $checksum += i2c_smbus_read_byte_data($file,$i);
  }
  $checksum &= 255;
  return 8
    if (i2c_smbus_read_byte_data($file,63) == $checksum);
  # Even if checksum test fails, it still may be an eeprom
  if (i2c_smbus_read_byte_data($file,2) == 0
   && i2c_smbus_read_byte_data($file,3) == 0
   && i2c_smbus_read_byte_data($file,4) == 0
   && i2c_smbus_read_byte_data($file,5) == 0)
  {
    if (i2c_smbus_read_byte_data($file,128) == ord 'P'
     && i2c_smbus_read_byte_data($file,129) == ord 'C'
     && i2c_smbus_read_byte_data($file,130) == ord 'G'
     && i2c_smbus_read_byte_data($file,131) == ord '-')
    {
      # Sony Vaio
      return 8 if ($addr == 0x57);
      return 6;
    }
    return 2;
  }
  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, (1) if detected.
# Detection is impossible!
sub ltc1710_detect
{
  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, (1) if detected.
# Registers used:
#   0x00..0x07: DDC signature
#   0x08..0x7E: checksumed area
#   0x7F:       checksum
### commented out additional location checks for now - don't work?
sub ddcmonitor_detect
{
  my ($file,$addr) = @_;
  my $i;
###  for ($i = 0; $i < 8; $i ++) {
###    i2c_set_slave_addr \*FILE,$addr+$i or goto FAILURE;
    i2c_smbus_read_byte_data($file,0x00) == 0x00 or goto FAILURE;
    i2c_smbus_read_byte_data($file,0x01) == 0xFF or goto FAILURE;
    i2c_smbus_read_byte_data($file,0x02) == 0xFF or goto FAILURE;
    i2c_smbus_read_byte_data($file,0x03) == 0xFF or goto FAILURE;
    i2c_smbus_read_byte_data($file,0x04) == 0xFF or goto FAILURE;
    i2c_smbus_read_byte_data($file,0x05) == 0xFF or goto FAILURE;
    i2c_smbus_read_byte_data($file,0x06) == 0xFF or goto FAILURE;
    i2c_smbus_read_byte_data($file,0x07) == 0x00 or goto FAILURE;
###  }
###  i2c_set_slave_addr \*FILE,$addr or return;
  # Check the checksum for validity. We should do this for all addresses,
  # but it would be too slow.
  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);
FAILURE:
  i2c_set_slave_addr \*FILE,$addr;
  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, (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: 1
# Detection is impossible...
sub pcf8591_detect
{
  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: 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);
}

# $_[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
sub smartbatt_detect
{
  my ($file,$addr) = @_;
  # check some registers
  if (i2c_smbus_read_byte_data($file,0x08) == 0xff) {
  	return;
  }
  if (i2c_smbus_read_byte_data($file,0x09) != 0xff) {
  	return;
  }
  if (i2c_smbus_read_byte_data($file,0x0a) != 0xff) {
  	return;
  }
  return (3);
}

# 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 = ..., 1 = ...)
# $_[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);
}



################
# 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' (%s)\n", $data->{i2c_adap}, $data->{i2c_algo};
      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 {
        printf "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 @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
  open INPUTFILE,"/proc/bus/i2c" or die "Couldn't open /proc/bus/i2c?!?";
  while (<INPUTFILE>) {
    my ($dev_nr,$type,$adap,$algo) = /^i2c-(\S+)\s+(\S+)\s+(.*?)\s*\t\s*(.*?)\s+$/;
    next if ($type eq "dummy");
    $adapters[$dev_nr]->{driver} = find_adapter_driver($adap,$algo);
    $adapters[$dev_nr]->{adapname} = $adap;
    $adapters[$dev_nr]->{algoname} = $algo;
  }
  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) {
           $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;
      }
    }
  }

  for ($i = 0; $i < $nr; $i++) {
    foreach $adap (@adapters) {
      if ($adap->{driver} eq "UNKNOWN") {
        $modprobes .= "# modprobe unknown adapter ".$adap->{adapname}." using ". $adap->{algoname}."\n";
      } else {
        $modprobes .= "modprobe $adap->{driver}\n" if (defined($adap->{nr_later}) and $adap->{nr_later} == $i) and not $modprobes =~ /modprobe $adap->{driver}\n/;
      }
    }
  }
  $modprobes .= "modprobe i2c-isa\n" if ($isa);

  # Now determine the chip probe lines
  $modprobes .= "# I2C chip drivers\n";
  foreach $chip (@chips_detected) {
    next if not @{$chip->{detected}};
    $modprobes .= "modprobe $chip->{driver}\n";
    @optionlist = ();
    @probelist = ();

    # Handle detects at addresses normally not probed
    foreach $detection (@{$chip->{detected}}) {
      push @probelist, $adapters[$detection->{i2c_devnr}]->{nr_later},
                       $detection->{i2c_addr}
           if exists $detection->{i2c_addr} and
              exists $detection->{i2c_extra};
      push @probelist, -1, $detection->{isa_addr}
           if exists $detection->{isa_addr} and
              exists $detection->{isa_extra};
    }

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

# returns:
# system id string (e.g. 'IBM'), unsafe system
# '1', safe system
# 0, could not determine
sub system_safeness_by_dmi
{
  my $opened = 0;
  foreach my $dmidecode (@dmidecode)
  {
    last if (-r $dmidecode && ($opened = open (DMI, "$dmidecode |")));
  }

  unless ($opened)
  {
    print " Could not find dmidecode, which should have been installed with lm_sensors.\n",
          " Runing dmidecode would help us determining your system vendor, which allows\n",
          " safer operations. Please provide one of the following:\n  ";
    print join ("\n  ", @dmidecode);
    print "\n You can still go on, but you are encouraged to fix the problem first.\n\n";
    return 0;
  }

  my ($dmitype, $biosversion, $systemvendor);
  while (<DMI>)
  {
    if (m/^\s*DMI type (\d+),/)
    {
      $dmitype = $1;
      next;
    }
    next unless defined $dmitype;
    if ($dmitype == 1 && m/^\s*(Vendor|Manufacturer): (.*)$/)
    {
      $systemvendor = $2;
    }
    elsif ($dmitype == 0 && m/^\s*Version: (.*)$/)
    {
      $biosversion = $1;
    }
    last if (defined $biosversion && defined $systemvendor);
  }
  close (DMI);

  if (defined $systemvendor && $systemvendor !~ m/^\s*$/)
  {
    $systemvendor =~ s/\s*$//;
    print " System vendor (DMI): $systemvendor\n";
    if (defined $biosversion && $biosversion !~ m/^\s*$/)
    {
      $biosversion =~ s/\s*$//;
      print " BIOS version (DMI): $biosversion\n";
    }
    return 'IBM' if $systemvendor =~ /\bIBM\b/;
    return '1';
  }

  return undef;
}

# returns:
# system id string (e.g. 'IBM'), unsafe system
# '1', safe system
# 0, could not determine
sub system_safeness_by_acpi
{
  my $pos = 0xF0000;
  my $found = 0;
  my $oem = '';

  return 0
    unless open MEM, '/dev/mem';
  binmode MEM;
  unless (seek MEM, $pos, SEEK_SET)
  {
    close MEM;
    return 0;
  }
  while ($pos <= 0xFFFF0 && !$found)
  {
    my $r = read (MEM, my $buf, 16);
    unless ($r == 16)
    {
      close MEM;
      return 0;
    }
    if (substr ($buf, 0, 8) eq 'RSD PTR ')
    {
      $oem = substr ($buf, 9, 6);
      $found++;
    }
    $pos += 16;
  }
  close MEM;

  return 0 unless $found;
  print " BIOS vendor (ACPI): $oem\n";
  return 'IBM' if $oem eq 'IBM   ';
  return '1';
}

# returns:
# 1 : the system is known to be safe
# 0 : the system safeness is unknown
# If the system is know to be unsafe (i.e. for now, IBM systems), never
#   return.
sub safe_system_vendor
{
  if ($> != 0)
  {
  	print " As you are not root, we can't determine your system vendor.\n";
  	return 0;
  }

  # We now have two methods for detecting IBM systems: ACPI and DMI.
  # The ACPI scan is easy and handled internally. The DMI scan, being more
  # complex, is handled by dmidecode, externally. Each method can return
  # three status:
  #  * the system is known to be safe (returns '1');
  #  * the system is known to be unsafe (returns a string that identifies
  #    the system, e.g. 'IBM');
  #  * the method doesn't permit to detect wether the system is safe
  #    (returns 0).
  # We then combine both results to come to a conclusion. The rules we
  # follow are (in order):
  #  * if both methods return 0, we can't say anything and return 0 (meaning
  #    "system safeness is unknown");
  #  * else, if no method returns an identifier string (that is, each method
  #    returns either 0 or '1'), we assume that the system is safe and
  #    return 1 (meaning "system is safe");
  #  * else display an alert message and exit; if only one of the methods
  #    worked, ask the user to be kind and send us a report.

  my $safeness_acpi = system_safeness_by_acpi();
  my $safeness_dmi = system_safeness_by_dmi();

  return 0
  	unless ($safeness_acpi || $safeness_dmi);

  return 1
    if ((!$safeness_acpi || $safeness_acpi eq '1')
         && (!$safeness_dmi || $safeness_dmi eq '1'));

  my $safeness = (!$safeness_acpi || $safeness_acpi eq '1')?
    $safeness_dmi:$safeness_acpi;

  print " Sorry, we won't let you go on. $safeness systems are known to have\n",
        " serious problems with lm_sensors, resulting in hardware failures.\n",
        " For more information, see README.thinkpad or\n",
        " http://www2.lm-sensors.nu/~lm78/cvs/lm_sensors2/README.thinkpad.\n";

  if($safeness_acpi eq '1' || $safeness_dmi eq '1')
  {
    print " We used two methods to determine your system's vendor, and they led\n",
          " to different results. We'd appreciate to have feedback about such\n",
          " systems. Please, take some time and contact the lm_sensors mailing\n",
          " list at <sensors\@stimpy.netroedge.com>.\n",
          " We need the following information:\n",
          "  * The brand and model of your system\n",
          "  * The BIOS vendor (ACPI) displayed above\n",
          "  * The System vendor (DMI) displayed above\n",
          " Thanks!\n";
  }

  exit;
}

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

  initialize_proc_pci;
  initialize_modules_list;

  print " This program will help you to determine which I2C/SMBus modules you ",
        "need to\n",
        " load to use lm_sensors most effectively.\n";
  print " You need to have done a `make install', issued a `depmod -a' and ",
        "made sure\n",
        " `/etc/conf.modules' (or `/etc/modules.conf') contains the ",
        "appropriate\n",
        " module path before you can use some functions of this utility. ",
        "Read\n",
        " doc/modules for more information.\n";
  print " Also, you need to be `root', or at least have access to the ",
        "/dev/i2c[-/]* files\n",
        " for some things. You can use prog/mkdev/mkdev.sh to create these ",
        "/dev files\n",
        " if you do not have them already.\n";
  print " If you have patched your kernel and have some drivers built-in ",
        "you can\n",
        " safely answer NO if asked to load some modules. In this case, ",
        "things may\n",
        " seem a bit confusing, but they will still work.\n\n";

  unless (safe_system_vendor ())
  {
    print " IF THIS IS AN IBM THINKPAD, PRESS CTRL-C NOW!\n";
    print " IBM Thinkpads have a severely broken i2c/SMBus implementation, ";
    print "just scanning\n";
    print " the bus will break your Thinkpad forever!\n";
    print " If this is a non-Thinkpad IBM, we still suggest you press CTRL+C. We have\n";
    print " had users reporting system breakage on other IBM systems as well.\n\n";
  }

  print " We can start with probing for (PCI) I2C or SMBus adapters.\n";
  print " You do not need any special privileges for this.\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 adapter\n",
          " modules. You can still be prompted for non-detectable adapters.\n",
          " Do you want to? (yes/NO): ";
    $detect_others = <STDIN> =~ /^\s*[Yy]/;
  } elsif ($> != 0) {
    print " As you are not root, we can't load adapter modules. We will only ",
          "scan\n",
          " already loaded adapters.\n";
    $detect_others = 0;
  } else {
    print " We will now try to load each adapter module in turn.\n";
    foreach $adapter (@adapters) {
      if (contains $adapter, @modules_list) {
        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";
            if ($adapter eq "i2c-riva") {
              print "** Note: i2c-riva module is available at \n";
              print "** http://drama.obuda.kando.hu/~fero/cgi-bin/rivatv.shtml\n"; 
            }
          } else {
            print "Module loaded succesfully.\n";
          }
        }
      }
    }
    print " Do you now want to be prompted for non-detectable adapters? ",
          "(yes/NO): ";
    $detect_others = <STDIN> =~ /^\s*[Yy]/ ;
  }

  if ($detect_others) {
    foreach $adapter (@undetectable_adapters) {
      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 succesfully.\n";
        }
      }
    }
  }

  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";
  if (contains "i2c-dev", @modules_list) {
    print "i2c-dev is already loaded.\n";
  } else {
    if ($> != 0) {
      print " i2c-dev is not loaded. As you are not root, we will just hope ",
            "you edited\n",
            " `/etc/conf.modules' (or `/etc/modules.conf') for automatic ",
            "loading of\n",
            " this module. If not, you won't be able to open any /dev/i2c[-/]* ",
            "file.\n";
    } else {
      print " i2c-dev is not loaded. Do you want to load it now? (YES/no): ";
      if (<STDIN> =~ /^\s*[Nn]/) {
        print " Well, you will know best. We will just hope you edited ",
              "`/etc/conf.modules'\n",
              " (or `/etc/modules.conf') for automatic loading of this ",
              "module. If not,\n",
              " you won't be able to open any /dev/i2c[-/]* file (unless you",
              "have it built-in\n",
              " into your kernel)\n";
      } elsif (system "modprobe","i2c-dev") {
        print " Loading failed, expect problems later on.\n";
      } else {
        print " Module loaded succesfully.\n";
      }
    }
  }

  print "\n We are now going to do the adapter probings. Some adapters may ",
        "hang halfway\n",
        " through; we can't really help that. Also, some chips will be double ",
        "detected;\n",
        " we choose the one with the highest confidence value in that case.\n",
        " If you found that the adapter hung after probing a certain address, ",
        "you can\n",
        " specify that address to remain unprobed. That ",
        "often\n",
        " includes address 0x69 (clock chip).\n";

  my ($inp,@not_to_scan,$inp2);
  open INPUTFILE,"/proc/bus/i2c" or die "Couldn't open /proc/bus/i2c?!?";
  while (<INPUTFILE>) {
    my ($dev_nr,$type,$adap,$algo) = /^i2c-(\S+)\s+(\S+)\s+(.*?)\s*\t\s*(.*?)\s+$/;
    next if ($type eq "dummy");
    print "\n";
    print "Next adapter: $adap ($algo)\n";
    print "Do you want to scan it? (YES/no/selectively): ";
    
    $inp = <STDIN>;
    @not_to_scan=();
    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, $algo, find_adapter_driver($adap,$algo),
                 \@not_to_scan   unless $inp =~ /^\s*[Nn]/;
  }

  print "\n Some chips are also accessible through the ISA bus. ISA probes ",
        "are\n",
        " typically a bit more dangerous, as we have to write to I/O ports ",
        "to do\n",
        " this. ";
  if ($> != 0) {
    print "As you are not root, we shall skip this step.\n";
  } else {
    print " Do you want to scan the ISA bus? (YES/no): ";
    if (not <STDIN> =~ /^\s*[Nn]/) {
      initialize_ioports();
      scan_isa_bus();
      close_ioports();
    }
  }

  print "\n Some Super I/O chips may also contain sensors. Super I/O probes ",
        "are\n",
        " typically a bit more dangerous, as we have to write to I/O ports ",
        "to do\n",
        " this. ";
  if ($> != 0) {
    print "As you are not root, we shall skip this step.\n";
  } else {
    print " Do you want to scan for Super I/O sensors? (YES/no): ";
    if (not <STDIN> =~ /^\s*[Nn]/) {
      initialize_ioports();
      scan_superio();
      close_ioports();
    }
  }

  if(! @chips_detected) {
    print "\n Sorry, no chips were detected.\n",
        " Either your sensors are not supported, or they are\n",
	" connected to an I2C bus adapter that we do not support.\n",
	" See doc/FAQ, doc/lm_sensors-FAQ.html, or\n",
	" http://www2.lm-sensors.nu/~lm78/cvs/lm_sensors2/doc/lm_sensors-FAQ.html\n",
	" (FAQ #4.24.3) for further information.\n",
	" If you find out what chips are on your board, see\n",
	" http://secure.netroedge.com/~lm78/newdrivers.html for driver status.\n";
    exit;
  }

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

  my ($chip,$data);
  foreach $chip (@chips_detected) {
    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};
    }
  }

  print "\n\n",
        " I will now generate the commands needed to load the I2C modules.\n",
        " Sometimes, a chip is available both through the ISA bus and an ",
        "I2C bus.\n",
        " ISA bus access is faster, but you need to load an additional driver ",
        "module\n",
        " for it. If you have the choice, do you want to use the ISA bus or ",
        "the\n",
        " I2C/SMBus (ISA/smbus)? ";
  my $use_isa = not <STDIN> =~ /\s*[Ss]/;
     
  my ($modprobes,$configfile) = generate_modprobes $use_isa;
  print "\nWARNING! If you have some things built into your kernel, the \n",
        "below list will contain too many modules. Skip the appropriate ones!";
  print "\nTo load everything that is needed, add this to some /etc/rc* ",
        "file:\n\n";
  print "#----cut here----\n";
  print $modprobes;
  print "# Next 2 lines are optional\n",
        "sleep 3\n",
        "/usr/local/bin/sensors -s\n";
  print "#----cut here----\n";
  print "\nTo make the sensors modules behave correctly, add these lines to ",
        "either\n",
        "/etc/modules.conf or /etc/conf.modules:\n\n";
  print "#----cut here----\n";
  print $configfile;
  print "#----cut here----\n";
  
  my $have_sysconfig = -d '/etc/sysconfig';
  print "\nDo you want to generate /etc/sysconfig/lm_sensors? (".
    ($have_sysconfig?"YES/no":"yes/NO")."): ";
  if ($> != 0) {
    print "\nAs you are not root, we shall skip this step.\n";
  } else {
    if (($have_sysconfig and not <STDIN> =~ /^\s*[Nn]/) or <STDIN> =~ /^\s*[Yy]/) {
      unless ($have_sysconfig) {
        mkdir '/etc/sysconfig', 0777
          or die "Sorry, can't create /etc/sysconfig ($!)?!?";
      }
      open(SYSCONFIG, ">/etc/sysconfig/lm_sensors")
        or die "Sorry, can't create /etc/sysconfig/lm_sensors ($!)?!?";
      print SYSCONFIG <<'EOT';
#    /etc/sysconfig/sensors - Defines modules loaded by /etc/rc.d/init.d/lm_sensors
#    Copyright (c) 1998 - 2001  Frodo Looijaard <frodol@dds.nl>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
#
# See also the lm_sensors homepage at:
#     http://www2.lm-sensors.nu/~lm78/index.html
#
# This file is used by /etc/rc.d/init.d/lm_sensors and defines the modules to
# be loaded/unloaded. This file is sourced into /etc/rc.d/init.d/lm_sensors.
#
# The format of this file is a shell script that simply defines the modules
# in order as normal variables with the special names:
#    MODULE_0, MODULE_1, MODULE_2, etc.
#
# List the modules that are to be loaded for your system
#
EOT
      print SYSCONFIG
       "# Generated by sensors-detect on " . scalar localtime() . "\n";
      my @modules = grep /^modprobe /, split "\n", $modprobes;
      my $i = 0;
      my $sysconfig = "";
      foreach (@modules) {
        s/^modprobe //;
        $sysconfig .= "MODULE_$i=$_\n";
        $i++;
      }
      print SYSCONFIG $sysconfig;
      close(SYSCONFIG);
      print "Copy prog/init/lm_sensors.init to /etc/rc.d/init.d/lm_sensors\n";
      print "for initialization at boot time.\n";
    }
  }
}

main;
