|  | /* | 
|  | *    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.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 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, 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); |