Subversion Repositories shark

Rev

Blame | Last modification | View Log | RSS feed

/*
 *    kernel/busses/i2c-prosavage.c
 *
 *    i2c bus driver for S3/VIA 8365/8375 graphics processor.
 *    Copyright (c) 2003 Henk Vergonet <henk@god.dyndns.org>
 *    Based on code written by:
 *      Frodo Looijaard <frodol@dds.nl>,
 *      Philip Edelbrock <phil@netroedge.com>,
 *      Ralph Metzler <rjkm@thp.uni-koeln.de>, and
 *      Mark D. Studebaker <mdsxyz123@yahoo.com>
 *      Simon Vogl
 *      and others
 *
 *    Please read the lm_sensors documentation for details on use.
 *
 *    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.
 *
 */

/*  18-05-2003 HVE - created
 *  14-06-2003 HVE - adapted for lm_sensors2
 *  17-06-2003 HVE - linux 2.5.xx compatible
 *  18-06-2003 HVE - codingstyle
 *  21-06-2003 HVE - compatibility lm_sensors2 and linux 2.5.xx
 *                   codingstyle, mmio enabled
 *
 *  This driver interfaces to the I2C bus of the VIA north bridge embedded
 *  ProSavage4/8 devices. Usefull for gaining access to the TV Encoder chips.
 *
 *  Graphics cores:
 *   S3/VIA KM266/VT8375 aka ProSavage8
 *   S3/VIA KM133/VT8365 aka Savage4
 *
 *  Two serial busses are implemented:
 *   SERIAL1 - I2C serial communications interface
 *   SERIAL2 - DDC2 monitor communications interface
 *
 *  Tested on a FX41 mainboard, see http://www.shuttle.com
 *
 *
 *  TODO:
 *  - integration with prosavage framebuffer device
 *    (Additional documentation needed :(
 */


#include <linux/module.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/i2c.h>
#include <linux/i2c-algo-bit.h>
#include <asm/io.h>

/*
 * driver configuration
 */

#define MAX_BUSSES      2

struct s_i2c_bus {
        void    *mmvga;
        int     i2c_reg;
        int     adap_ok;
        struct i2c_adapter              adap;
        struct i2c_algo_bit_data        algo;
};

struct s_i2c_chip {
        void    *mmio;
        struct s_i2c_bus        i2c_bus[MAX_BUSSES];
};


/*
 * i2c configuration
 */

#ifndef I2C_HW_B_S3VIA
#define I2C_HW_B_S3VIA  0x18    /* S3VIA ProSavage adapter              */
#endif

/* delays */
#define CYCLE_DELAY     10
#define TIMEOUT         (HZ / 2)


/*
 * S3/VIA 8365/8375 registers
 */

#ifndef PCI_DEVICE_ID_S3_SAVAGE4
#define PCI_DEVICE_ID_S3_SAVAGE4        0x8a25
#endif
#ifndef PCI_DEVICE_ID_S3_PROSAVAGE8
#define PCI_DEVICE_ID_S3_PROSAVAGE8     0x8d04
#endif

#define VGA_CR_IX       0x3d4
#define VGA_CR_DATA     0x3d5

#define CR_SERIAL1      0xa0    /* I2C serial communications interface */
#define MM_SERIAL1      0xff20
#define CR_SERIAL2      0xb1    /* DDC2 monitor communications interface */

/* based on vt8365 documentation */
#define I2C_ENAB        0x10
#define I2C_SCL_OUT     0x01
#define I2C_SDA_OUT     0x02
#define I2C_SCL_IN      0x04
#define I2C_SDA_IN      0x08

#define SET_CR_IX(p, val)       writeb((val), (p)->mmvga + VGA_CR_IX)
#define SET_CR_DATA(p, val)     writeb((val), (p)->mmvga + VGA_CR_DATA)
#define GET_CR_DATA(p)          readb((p)->mmvga + VGA_CR_DATA)


/*
 * Serial bus line handling
 *
 * serial communications register as parameter in private data
 *
 * TODO: locks with other code sections accessing video registers?
 */

static void bit_s3via_setscl(void *bus, int val)
{
        struct s_i2c_bus *p = (struct s_i2c_bus *)bus;
        unsigned int r;

        SET_CR_IX(p, p->i2c_reg);
        r = GET_CR_DATA(p);
        r |= I2C_ENAB;
        if (val) {
                r |= I2C_SCL_OUT;
        } else {
                r &= ~I2C_SCL_OUT;
        }
        SET_CR_DATA(p, r);
}

static void bit_s3via_setsda(void *bus, int val)
{
        struct s_i2c_bus *p = (struct s_i2c_bus *)bus;
        unsigned int r;
       
        SET_CR_IX(p, p->i2c_reg);
        r = GET_CR_DATA(p);
        r |= I2C_ENAB;
        if (val) {
                r |= I2C_SDA_OUT;
        } else {
                r &= ~I2C_SDA_OUT;
        }
        SET_CR_DATA(p, r);
}

static int bit_s3via_getscl(void *bus)
{
        struct s_i2c_bus *p = (struct s_i2c_bus *)bus;

        SET_CR_IX(p, p->i2c_reg);
        return (0 != (GET_CR_DATA(p) & I2C_SCL_IN));
}

static int bit_s3via_getsda(void *bus)
{
        struct s_i2c_bus *p = (struct s_i2c_bus *)bus;

        SET_CR_IX(p, p->i2c_reg);
        return (0 != (GET_CR_DATA(p) & I2C_SDA_IN));
}


/*
 * adapter initialisation
 */

static int i2c_register_bus(struct pci_dev *dev, struct s_i2c_bus *p, u8 *mmvga, u32 i2c_reg)
{
        int ret;
        p->adap.owner     = THIS_MODULE;
        p->adap.id        = I2C_HW_B_S3VIA;
        p->adap.algo_data = &p->algo;
        p->adap.dev.parent = &dev->dev;
        p->algo.setsda    = bit_s3via_setsda;
        p->algo.setscl    = bit_s3via_setscl;
        p->algo.getsda    = bit_s3via_getsda;
        p->algo.getscl    = bit_s3via_getscl;
        p->algo.udelay    = CYCLE_DELAY;
        p->algo.mdelay    = CYCLE_DELAY;
        p->algo.timeout   = TIMEOUT;
        p->algo.data      = p;
        p->mmvga          = mmvga;
        p->i2c_reg        = i2c_reg;
   
        ret = i2c_bit_add_bus(&p->adap);
        if (ret) {
                return ret;
        }

        p->adap_ok = 1;
        return 0;
}


/*
 * Cleanup stuff
 */

static void __devexit prosavage_remove(struct pci_dev *dev)
{
        struct s_i2c_chip *chip;
        int i, ret;

        chip = (struct s_i2c_chip *)pci_get_drvdata(dev);

        if (!chip) {
                return;
        }
        for (i = MAX_BUSSES - 1; i >= 0; i--) {
                if (chip->i2c_bus[i].adap_ok == 0)
                        continue;

                ret = i2c_bit_del_bus(&chip->i2c_bus[i].adap);
                if (ret) {
                        dev_err(&dev->dev, ": %s not removed\n",
                                chip->i2c_bus[i].adap.name);
                }
        }
        if (chip->mmio) {
                iounmap(chip->mmio);
        }
        kfree(chip);
}


/*
 * Detect chip and initialize it
 */

static int __devinit prosavage_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
        int ret;
        unsigned long base, len;
        struct s_i2c_chip *chip;
        struct s_i2c_bus  *bus;

        pci_set_drvdata(dev, kmalloc(sizeof(struct s_i2c_chip), GFP_KERNEL));
        chip = (struct s_i2c_chip *)pci_get_drvdata(dev);
        if (chip == NULL) {
                return -ENOMEM;
        }

        memset(chip, 0, sizeof(struct s_i2c_chip));

        base = dev->resource[0].start & PCI_BASE_ADDRESS_MEM_MASK;
        len  = dev->resource[0].end - base + 1;
        chip->mmio = ioremap_nocache(base, len);

        if (chip->mmio == NULL) {
                dev_err(&dev->dev, "ioremap failed\n");
                prosavage_remove(dev);
                return -ENODEV;
        }


        /*
         * Chip initialisation
         */

        /* Unlock Extended IO Space ??? */


        /*
         * i2c bus registration
         */

        bus = &chip->i2c_bus[0];
        snprintf(bus->adap.name, sizeof(bus->adap.name),
                "ProSavage I2C bus at %02x:%02x.%x",
                dev->bus->number, PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn));
        ret = i2c_register_bus(dev, bus, chip->mmio + 0x8000, CR_SERIAL1);
        if (ret) {
                goto err_adap;
        }
        /*
         * ddc bus registration
         */

        bus = &chip->i2c_bus[1];
        snprintf(bus->adap.name, sizeof(bus->adap.name),
                "ProSavage DDC bus at %02x:%02x.%x",
                dev->bus->number, PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn));
        ret = i2c_register_bus(dev, bus, chip->mmio + 0x8000, CR_SERIAL2);
        if (ret) {
                goto err_adap;
        }
        return 0;
err_adap:
        dev_err(&dev->dev, ": %s failed\n", bus->adap.name);
        prosavage_remove(dev);
        return ret;
}


/*
 * Data for PCI driver interface
 */

static struct pci_device_id prosavage_pci_tbl[] = {
        { PCI_DEVICE(PCI_VENDOR_ID_S3, PCI_DEVICE_ID_S3_SAVAGE4) },
        { PCI_DEVICE(PCI_VENDOR_ID_S3, PCI_DEVICE_ID_S3_PROSAVAGE8) },
        { 0, },
};

static struct pci_driver prosavage_driver = {
        .name           =       "prosavage-smbus",
        .id_table       =       prosavage_pci_tbl,
        .probe          =       prosavage_probe,
        .remove         =       __devexit_p(prosavage_remove),
};

static int __init i2c_prosavage_init(void)
{
        return pci_module_init(&prosavage_driver);
}

static void __exit i2c_prosavage_exit(void)
{
        pci_unregister_driver(&prosavage_driver);
}

MODULE_DEVICE_TABLE(pci, prosavage_pci_tbl);
MODULE_AUTHOR("Henk Vergonet");
MODULE_DESCRIPTION("ProSavage VIA 8365/8375 smbus driver");
MODULE_LICENSE("GPL");

module_init (i2c_prosavage_init);
module_exit (i2c_prosavage_exit);