/*
 *    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 __iomem *mmvga;
	int	i2c_reg;
	int	adap_ok;
	struct i2c_adapter		adap;
	struct i2c_algo_bit_data	algo;
};

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


/*
 * i2c configuration
 */
#define CYCLE_DELAY	10
#define TIMEOUT		(HZ / 2)


/* 
 * S3/VIA 8365/8375 registers
 */
#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, void __iomem *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.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 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_del_adapter(&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, kzalloc(sizeof(struct s_i2c_chip), GFP_KERNEL));
	chip = (struct s_i2c_chip *)pci_get_drvdata(dev);
	if (chip == NULL) {
		return -ENOMEM;
	}

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

MODULE_DEVICE_TABLE (pci, prosavage_pci_tbl);

static struct pci_driver prosavage_driver = {
	.name		=	"prosavage_smbus",
	.id_table	=	prosavage_pci_tbl,
	.probe		=	prosavage_probe,
	.remove		=	prosavage_remove,
};

static int __init i2c_prosavage_init(void)
{
	return pci_register_driver(&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);
