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