Subversion Repositories shark

Rev

Blame | Last modification | View Log | RSS feed

/*
    i2c-viapro.c - Part of lm_sensors, Linux kernel modules for hardware
              monitoring
    Copyright (c) 1998 - 2002  Frodo Looijaard <frodol@dds.nl>,
    Philip Edelbrock <phil@netroedge.com>, Kyösti Mälkki <kmalkki@cc.hut.fi>,
    Mark D. Studebaker <mdsxyz123@yahoo.com>

    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.
*/


/*
   Supports Via devices:
        82C596A/B (0x3050)
        82C596B (0x3051)
        82C686A/B
        8231
        8233
        8233A (0x3147 and 0x3177)
        8235
   Note: we assume there can only be one device, with one SMBus interface.
*/


#include <linux/module.h>
#include <linux/pci.h>
#include <linux/kernel.h>
#include <linux/stddef.h>
#include <linux/sched.h>
#include <linux/ioport.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <asm/io.h>

#define SMBBA1           0x90
#define SMBBA2           0x80
#define SMBBA3           0xD0

/* SMBus address offsets */
static unsigned short vt596_smba;
#define SMBHSTSTS       (vt596_smba + 0)
#define SMBHSLVSTS      (vt596_smba + 1)
#define SMBHSTCNT       (vt596_smba + 2)
#define SMBHSTCMD       (vt596_smba + 3)
#define SMBHSTADD       (vt596_smba + 4)
#define SMBHSTDAT0      (vt596_smba + 5)
#define SMBHSTDAT1      (vt596_smba + 6)
#define SMBBLKDAT       (vt596_smba + 7)
#define SMBSLVCNT       (vt596_smba + 8)
#define SMBSHDWCMD      (vt596_smba + 9)
#define SMBSLVEVT       (vt596_smba + 0xA)
#define SMBSLVDAT       (vt596_smba + 0xC)

/* PCI Address Constants */

/* SMBus data in configuration space can be found in two places,
   We try to select the better one*/


static unsigned short smb_cf_hstcfg = 0xD2;

#define SMBHSTCFG   (smb_cf_hstcfg)
#define SMBSLVC     (smb_cf_hstcfg + 1)
#define SMBSHDW1    (smb_cf_hstcfg + 2)
#define SMBSHDW2    (smb_cf_hstcfg + 3)
#define SMBREV      (smb_cf_hstcfg + 4)

/* Other settings */
#define MAX_TIMEOUT     500
#define ENABLE_INT9     0

/* VT82C596 constants */
#define VT596_QUICK      0x00
#define VT596_BYTE       0x04
#define VT596_BYTE_DATA  0x08
#define VT596_WORD_DATA  0x0C
#define VT596_BLOCK_DATA 0x14


/* If force is set to anything different from 0, we forcibly enable the
   VT596. DANGEROUS! */

static int force;
MODULE_PARM(force, "i");
MODULE_PARM_DESC(force, "Forcibly enable the SMBus. DANGEROUS!");

/* If force_addr is set to anything different from 0, we forcibly enable
   the VT596 at the given address. VERY DANGEROUS! */

static int force_addr;
MODULE_PARM(force_addr, "i");
MODULE_PARM_DESC(force_addr,
                 "Forcibly enable the SMBus at the given address. "
                 "EXTREMELY DANGEROUS!");


static struct i2c_adapter vt596_adapter;

/* Another internally used function */
static int vt596_transaction(void)
{
        int temp;
        int result = 0;
        int timeout = 0;

        dev_dbg(&vt596_adapter.dev, "Transaction (pre): CNT=%02x, CMD=%02x, "
                "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT),
                inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0),
                inb_p(SMBHSTDAT1));

        /* Make sure the SMBus host is ready to start transmitting */
        if ((temp = inb_p(SMBHSTSTS)) != 0x00) {
                dev_dbg(&vt596_adapter.dev, "SMBus busy (0x%02x). "
                                "Resetting...\n", temp);
               
                outb_p(temp, SMBHSTSTS);
                if ((temp = inb_p(SMBHSTSTS)) != 0x00) {
                        dev_dbg(&vt596_adapter.dev, "Failed! (0x%02x)\n", temp);
                       
                        return -1;
                } else {
                        dev_dbg(&vt596_adapter.dev, "Successfull!\n");
                }
        }

        /* start the transaction by setting bit 6 */
        outb_p(inb(SMBHSTCNT) | 0x040, SMBHSTCNT);

        /* We will always wait for a fraction of a second!
           I don't know if VIA needs this, Intel did  */

        do {
                i2c_delay(1);
                temp = inb_p(SMBHSTSTS);
        } while ((temp & 0x01) && (timeout++ < MAX_TIMEOUT));

        /* If the SMBus is still busy, we give up */
        if (timeout >= MAX_TIMEOUT) {
                result = -1;
                dev_dbg(&vt596_adapter.dev, "SMBus Timeout!\n");
        }

        if (temp & 0x10) {
                result = -1;
                dev_dbg(&vt596_adapter.dev, "Error: Failed bus transaction\n");
        }

        if (temp & 0x08) {
                result = -1;
                dev_info(&vt596_adapter.dev, "Bus collision! SMBus may be "
                        "locked until next hard\nreset. (sorry!)\n");
                /* Clock stops and slave is stuck in mid-transmission */
        }

        if (temp & 0x04) {
                result = -1;
                dev_dbg(&vt596_adapter.dev, "Error: no response!\n");
        }

        if (inb_p(SMBHSTSTS) != 0x00)
                outb_p(inb(SMBHSTSTS), SMBHSTSTS);

        if ((temp = inb_p(SMBHSTSTS)) != 0x00) {
                dev_dbg(&vt596_adapter.dev, "Failed reset at end of "
                        "transaction (%02x)\n", temp);
        }
        dev_dbg(&vt596_adapter.dev, "Transaction (post): CNT=%02x, CMD=%02x, "
                "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT),
                inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0),
                inb_p(SMBHSTDAT1));
       
        return result;
}

/* Return -1 on error. */
static s32 vt596_access(struct i2c_adapter *adap, u16 addr,
                unsigned short flags,  char read_write, u8 command,
                int size,  union i2c_smbus_data *data)
{
        int i, len;

        switch (size) {
        case I2C_SMBUS_PROC_CALL:
                dev_info(&vt596_adapter.dev,
                         "I2C_SMBUS_PROC_CALL not supported!\n");
                return -1;
        case I2C_SMBUS_QUICK:
                outb_p(((addr & 0x7f) << 1) | (read_write & 0x01),
                       SMBHSTADD);
                size = VT596_QUICK;
                break;
        case I2C_SMBUS_BYTE:
                outb_p(((addr & 0x7f) << 1) | (read_write & 0x01),
                       SMBHSTADD);
                if (read_write == I2C_SMBUS_WRITE)
                        outb_p(command, SMBHSTCMD);
                size = VT596_BYTE;
                break;
        case I2C_SMBUS_BYTE_DATA:
                outb_p(((addr & 0x7f) << 1) | (read_write & 0x01),
                       SMBHSTADD);
                outb_p(command, SMBHSTCMD);
                if (read_write == I2C_SMBUS_WRITE)
                        outb_p(data->byte, SMBHSTDAT0);
                size = VT596_BYTE_DATA;
                break;
        case I2C_SMBUS_WORD_DATA:
                outb_p(((addr & 0x7f) << 1) | (read_write & 0x01),
                       SMBHSTADD);
                outb_p(command, SMBHSTCMD);
                if (read_write == I2C_SMBUS_WRITE) {
                        outb_p(data->word & 0xff, SMBHSTDAT0);
                        outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1);
                }
                size = VT596_WORD_DATA;
                break;
        case I2C_SMBUS_BLOCK_DATA:
                outb_p(((addr & 0x7f) << 1) | (read_write & 0x01),
                       SMBHSTADD);
                outb_p(command, SMBHSTCMD);
                if (read_write == I2C_SMBUS_WRITE) {
                        len = data->block[0];
                        if (len < 0)
                                len = 0;
                        if (len > 32)
                                len = 32;
                        outb_p(len, SMBHSTDAT0);
                        i = inb_p(SMBHSTCNT);   /* Reset SMBBLKDAT */
                        for (i = 1; i <= len; i++)
                                outb_p(data->block[i], SMBBLKDAT);
                }
                size = VT596_BLOCK_DATA;
                break;
        }

        outb_p((size & 0x1C) + (ENABLE_INT9 & 1), SMBHSTCNT);

        if (vt596_transaction()) /* Error in transaction */
                return -1;

        if ((read_write == I2C_SMBUS_WRITE) || (size == VT596_QUICK))
                return 0;

        switch (size) {
        case VT596_BYTE:
                /* Where is the result put? I assume here it is in
                 * SMBHSTDAT0 but it might just as well be in the
                 * SMBHSTCMD. No clue in the docs
                 */

                data->byte = inb_p(SMBHSTDAT0);
                break;
        case VT596_BYTE_DATA:
                data->byte = inb_p(SMBHSTDAT0);
                break;
        case VT596_WORD_DATA:
                data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8);
                break;
        case VT596_BLOCK_DATA:
                data->block[0] = inb_p(SMBHSTDAT0);
                i = inb_p(SMBHSTCNT);   /* Reset SMBBLKDAT */
                for (i = 1; i <= data->block[0]; i++)
                        data->block[i] = inb_p(SMBBLKDAT);
                break;
        }
        return 0;
}

static u32 vt596_func(struct i2c_adapter *adapter)
{
        return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
            I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
            I2C_FUNC_SMBUS_BLOCK_DATA;
}

static struct i2c_algorithm smbus_algorithm = {
        .name           = "Non-I2C SMBus adapter",
        .id             = I2C_ALGO_SMBUS,
        .smbus_xfer     = vt596_access,
        .functionality  = vt596_func,
};

static struct i2c_adapter vt596_adapter = {
        .owner          = THIS_MODULE,
        .class          = I2C_ADAP_CLASS_SMBUS,
        .algo           = &smbus_algorithm,
        .name           = "unset",
};

static int __devinit vt596_probe(struct pci_dev *pdev,
                                 const struct pci_device_id *id)
{
        unsigned char temp;
        int error = -ENODEV;
       
        /* Determine the address of the SMBus areas */
        if (force_addr) {
                vt596_smba = force_addr & 0xfff0;
                force = 0;
                goto found;
        }

        if ((pci_read_config_word(pdev, id->driver_data, &vt596_smba)) ||
            !(vt596_smba & 0x1)) {
                /* try 2nd address and config reg. for 596 */
                if (id->device == PCI_DEVICE_ID_VIA_82C596_3 &&
                    !pci_read_config_word(pdev, SMBBA2, &vt596_smba) &&
                    (vt596_smba & 0x1)) {
                        smb_cf_hstcfg = 0x84;
                } else {
                        /* no matches at all */
                        dev_err(&pdev->dev, "Cannot configure "
                                "SMBus I/O Base address\n");
                        return -ENODEV;
                }
        }

        vt596_smba &= 0xfff0;
        if (vt596_smba == 0) {
                dev_err(&pdev->dev, "SMBus base address "
                        "uninitialized - upgrade BIOS or use "
                        "force_addr=0xaddr\n");
                return -ENODEV;
        }

 found:
        if (!request_region(vt596_smba, 8, "viapro-smbus")) {
                dev_err(&pdev->dev, "SMBus region 0x%x already in use!\n",
                        vt596_smba);
                return -ENODEV;
        }

        pci_read_config_byte(pdev, SMBHSTCFG, &temp);
        /* If force_addr is set, we program the new address here. Just to make
           sure, we disable the VT596 first. */

        if (force_addr) {
                pci_write_config_byte(pdev, SMBHSTCFG, temp & 0xfe);
                pci_write_config_word(pdev, id->driver_data, vt596_smba);
                pci_write_config_byte(pdev, SMBHSTCFG, temp | 0x01);
                dev_warn(&pdev->dev, "WARNING: SMBus interface set to new "
                     "address 0x%04x!\n", vt596_smba);
        } else if ((temp & 1) == 0) {
                if (force) {
                        /* NOTE: This assumes I/O space and other allocations
                         * WERE done by the Bios!  Don't complain if your
                         * hardware does weird things after enabling this.
                         * :') Check for Bios updates before resorting to
                         * this.
                         */

                        pci_write_config_byte(pdev, SMBHSTCFG, temp | 1);
                        dev_info(&pdev->dev, "Enabling SMBus device\n");
                } else {
                        dev_err(&pdev->dev, "SMBUS: Error: Host SMBus "
                                "controller not enabled! - upgrade BIOS or "
                                "use force=1\n");
                        goto release_region;
                }
        }

        if ((temp & 0x0E) == 8)
                dev_dbg(&pdev->dev, "using Interrupt 9 for SMBus.\n");
        else if ((temp & 0x0E) == 0)
                dev_dbg(&pdev->dev, "using Interrupt SMI# for SMBus.\n");
        else
                dev_dbg(&pdev->dev, "Illegal Interrupt configuration "
                        "(or code out of date)!\n");

        pci_read_config_byte(pdev, SMBREV, &temp);
        dev_dbg(&pdev->dev, "SMBREV = 0x%X\n", temp);
        dev_dbg(&pdev->dev, "VT596_smba = 0x%X\n", vt596_smba);

        vt596_adapter.dev.parent = &pdev->dev;
        snprintf(vt596_adapter.name, I2C_NAME_SIZE,
                        "SMBus Via Pro adapter at %04x", vt596_smba);
       
        return i2c_add_adapter(&vt596_adapter);

 release_region:
        release_region(vt596_smba, 8);
        return error;
}

static void __devexit vt596_remove(struct pci_dev *pdev)
{
        i2c_del_adapter(&vt596_adapter);
        release_region(vt596_smba, 8);
}

static struct pci_device_id vt596_ids[] = {
        {
                .vendor         = PCI_VENDOR_ID_VIA,
                .device         = PCI_DEVICE_ID_VIA_82C596_3,
                .subvendor      = PCI_ANY_ID,
                .subdevice      = PCI_ANY_ID,
                .driver_data    = SMBBA1,
        },
        {
                .vendor         = PCI_VENDOR_ID_VIA,
                .device         = PCI_DEVICE_ID_VIA_82C596B_3,
                .subvendor      = PCI_ANY_ID,
                .subdevice      = PCI_ANY_ID,
                .driver_data    = SMBBA1,
        },
        {
                .vendor         = PCI_VENDOR_ID_VIA,
                .device         = PCI_DEVICE_ID_VIA_82C686_4,
                .subvendor      = PCI_ANY_ID,
                .subdevice      = PCI_ANY_ID,
                .driver_data    = SMBBA1,
        },
        {
                .vendor         = PCI_VENDOR_ID_VIA,
                .device         = PCI_DEVICE_ID_VIA_8233_0,
                .subvendor      = PCI_ANY_ID,
                .subdevice      = PCI_ANY_ID,
                .driver_data    = SMBBA3
        },
        {
                .vendor         = PCI_VENDOR_ID_VIA,
                .device         = PCI_DEVICE_ID_VIA_8233A,
                .subvendor      = PCI_ANY_ID,
                .subdevice      = PCI_ANY_ID,
                .driver_data    = SMBBA3,
        },
        {
                .vendor         = PCI_VENDOR_ID_VIA,
                .device         = PCI_DEVICE_ID_VIA_8235,
                .subvendor      = PCI_ANY_ID,
                .subdevice      = PCI_ANY_ID,
                .driver_data    = SMBBA3
        },
        {
                .vendor         = PCI_VENDOR_ID_VIA,
                .device         = PCI_DEVICE_ID_VIA_8231_4,
                .subvendor      = PCI_ANY_ID,
                .subdevice      = PCI_ANY_ID,
                .driver_data    = SMBBA1,
        },
        { 0, }
};

static struct pci_driver vt596_driver = {
        .name           = "vt596 smbus",
        .id_table       = vt596_ids,
        .probe          = vt596_probe,
        .remove         = __devexit_p(vt596_remove),
};

static int __init i2c_vt596_init(void)
{
        return pci_module_init(&vt596_driver);
}


static void __exit i2c_vt596_exit(void)
{
        pci_unregister_driver(&vt596_driver);
}

MODULE_AUTHOR(
    "Frodo Looijaard <frodol@dds.nl> and "
    "Philip Edelbrock <phil@netroedge.com>");
MODULE_DESCRIPTION("vt82c596 SMBus driver");
MODULE_LICENSE("GPL");

module_init(i2c_vt596_init);
module_exit(i2c_vt596_exit);