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