#include <linux/module.h>
#include <linux/pci.h>
#include <linux/kernel.h>
#include <linux/stddef.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <linux/i2c.h>
#include <linux/acpi.h>
#include <linux/io.h>
#define SMBHSTSTS (0 + ali1535_smba)
#define SMBHSTTYP (1 + ali1535_smba)
#define SMBHSTPORT (2 + ali1535_smba)
#define SMBHSTCMD (7 + ali1535_smba)
#define SMBHSTADD (3 + ali1535_smba)
#define SMBHSTDAT0 (4 + ali1535_smba)
#define SMBHSTDAT1 (5 + ali1535_smba)
#define SMBBLKDAT (6 + ali1535_smba)
#define SMBCOM 0x004
#define SMBREV 0x008
#define SMBCFG 0x0D1
#define SMBBA 0x0E2
#define SMBHSTCFG 0x0F0
#define SMBCLK 0x0F2
#define MAX_TIMEOUT 500 /* times 1/100 sec */
#define ALI1535_SMB_IOSIZE 32
#define ALI1535_SMB_DEFAULTBASE 0x8040
#define ALI1535_LOCK 0x06 /* dwe */
#define ALI1535_QUICK 0x00
#define ALI1535_BYTE 0x10
#define ALI1535_BYTE_DATA 0x20
#define ALI1535_WORD_DATA 0x30
#define ALI1535_BLOCK_DATA 0x40
#define ALI1535_I2C_READ 0x60
#define ALI1535_DEV10B_EN 0x80 /* Enable 10-bit addressing in */
#define ALI1535_T_OUT 0x08 /* Time-out Command (write) */
#define ALI1535_A_HIGH_BIT9 0x08 /* Bit 9 of 10-bit address in */
#define ALI1535_KILL 0x04 /* Kill Command (write) */
#define ALI1535_A_HIGH_BIT8 0x04 /* Bit 8 of 10-bit address in */
#define ALI1535_D_HI_MASK 0x03 /* Mask for isolating bits 9-8 */
#define ALI1535_STS_IDLE 0x04
#define ALI1535_STS_BUSY 0x08 /* host busy */
#define ALI1535_STS_DONE 0x10 /* transaction complete */
#define ALI1535_STS_DEV 0x20 /* device error */
#define ALI1535_STS_BUSERR 0x40 /* bus error */
#define ALI1535_STS_FAIL 0x80 /* failed bus transaction */
#define ALI1535_STS_ERR 0xE0 /* all the bad error bits */
#define ALI1535_BLOCK_CLR 0x04 /* reset block data index */
#define ALI1535_RD_ADDR 0x01 /* Read/Write Bit in Device */
#define ALI1535_SMBIO_EN 0x04 /* SMB I/O Space enable */
static struct pci_driver ali1535_driver;
static unsigned long ali1535_smba;
static unsigned short ali1535_offset;
static int ali1535_setup(struct pci_dev *dev)
{
int retval;
unsigned char temp;
retval = pci_enable_device(dev);
if (retval) {
dev_err(&dev->dev, "ALI1535_smb can't enable device\n");
goto exit;
}
pci_read_config_word(dev, SMBBA, &ali1535_offset);
dev_dbg(&dev->dev, "ALI1535_smb is at offset 0x%04x\n", ali1535_offset);
ali1535_offset &= (0xffff & ~(ALI1535_SMB_IOSIZE - 1));
if (ali1535_offset == 0) {
dev_warn(&dev->dev,
"ALI1535_smb region uninitialized - upgrade BIOS?\n");
retval = -ENODEV;
goto exit;
}
if (pci_resource_flags(dev, 0) & IORESOURCE_IO)
ali1535_smba = pci_resource_start(dev, 0) + ali1535_offset;
else
ali1535_smba = ali1535_offset;
retval = acpi_check_region(ali1535_smba, ALI1535_SMB_IOSIZE,
ali1535_driver.name);
if (retval)
goto exit;
if (!request_region(ali1535_smba, ALI1535_SMB_IOSIZE,
ali1535_driver.name)) {
dev_err(&dev->dev, "ALI1535_smb region 0x%lx already in use!\n",
ali1535_smba);
retval = -EBUSY;
goto exit;
}
pci_read_config_byte(dev, SMBCFG, &temp);
if ((temp & ALI1535_SMBIO_EN) == 0) {
dev_err(&dev->dev, "SMB device not enabled - upgrade BIOS?\n");
retval = -ENODEV;
goto exit_free;
}
pci_read_config_byte(dev, SMBHSTCFG, &temp);
if ((temp & 1) == 0) {
dev_err(&dev->dev, "SMBus controller not enabled - upgrade BIOS?\n");
retval = -ENODEV;
goto exit_free;
}
pci_write_config_byte(dev, SMBCLK, 0x20);
pci_read_config_byte(dev, SMBREV, &temp);
dev_dbg(&dev->dev, "SMBREV = 0x%X\n", temp);
dev_dbg(&dev->dev, "ALI1535_smba = 0x%lx\n", ali1535_smba);
return 0;
exit_free:
release_region(ali1535_smba, ALI1535_SMB_IOSIZE);
exit:
return retval;
}
static int ali1535_transaction(struct i2c_adapter *adap)
{
int temp;
int result = 0;
int timeout = 0;
dev_dbg(&adap->dev, "Transaction (pre): STS=%02x, TYP=%02x, "
"CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n",
inb_p(SMBHSTSTS), inb_p(SMBHSTTYP), inb_p(SMBHSTCMD),
inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), inb_p(SMBHSTDAT1));
temp = inb_p(SMBHSTSTS);
if (temp & ALI1535_STS_BUSY) {
dev_info(&adap->dev,
"Resetting entire SMB Bus to clear busy condition (%02x)\n",
temp);
outb_p(ALI1535_T_OUT, SMBHSTTYP);
temp = inb_p(SMBHSTSTS);
}
if (temp & (ALI1535_STS_ERR | ALI1535_STS_BUSY)) {
outb_p(0xFF, SMBHSTSTS);
temp = inb_p(SMBHSTSTS);
if (temp & (ALI1535_STS_ERR | ALI1535_STS_BUSY)) {
dev_err(&adap->dev,
"SMBus reset failed! (0x%02x) - controller or "
"device on bus is probably hung\n", temp);
return -EBUSY;
}
} else {
if (temp & ALI1535_STS_DONE)
outb_p(temp, SMBHSTSTS);
}
outb_p(0xFF, SMBHSTPORT);
timeout = 0;
do {
usleep_range(1000, 2000);
temp = inb_p(SMBHSTSTS);
} while (((temp & ALI1535_STS_BUSY) && !(temp & ALI1535_STS_IDLE))
&& (timeout++ < MAX_TIMEOUT));
if (timeout > MAX_TIMEOUT) {
result = -ETIMEDOUT;
dev_err(&adap->dev, "SMBus Timeout!\n");
}
if (temp & ALI1535_STS_FAIL) {
result = -EIO;
dev_dbg(&adap->dev, "Error: Failed bus transaction\n");
}
if (temp & ALI1535_STS_BUSERR) {
result = -ENXIO;
dev_dbg(&adap->dev,
"Error: no response or bus collision ADD=%02x\n",
inb_p(SMBHSTADD));
}
if (temp & ALI1535_STS_DEV) {
result = -EIO;
dev_err(&adap->dev, "Error: device error\n");
}
if (!(temp & ALI1535_STS_DONE)) {
result = -ETIMEDOUT;
dev_err(&adap->dev, "Error: command never completed\n");
}
dev_dbg(&adap->dev, "Transaction (post): STS=%02x, TYP=%02x, "
"CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n",
inb_p(SMBHSTSTS), inb_p(SMBHSTTYP), inb_p(SMBHSTCMD),
inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), inb_p(SMBHSTDAT1));
if (!(temp & ALI1535_STS_DONE)) {
outb_p(ALI1535_KILL, SMBHSTTYP);
outb_p(0xFF, SMBHSTSTS);
} else if (temp & ALI1535_STS_ERR) {
outb_p(ALI1535_T_OUT, SMBHSTTYP);
outb_p(0xFF, SMBHSTSTS);
}
return result;
}
static s32 ali1535_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;
int temp;
int timeout;
s32 result = 0;
temp = inb_p(SMBHSTSTS);
for (timeout = 0;
(timeout < MAX_TIMEOUT) && !(temp & ALI1535_STS_IDLE);
timeout++) {
usleep_range(1000, 2000);
temp = inb_p(SMBHSTSTS);
}
if (timeout >= MAX_TIMEOUT)
dev_warn(&adap->dev, "Idle wait Timeout! STS=0x%02x\n", temp);
outb_p(0xFF, SMBHSTSTS);
switch (size) {
case I2C_SMBUS_QUICK:
outb_p(((addr & 0x7f) << 1) | (read_write & 0x01),
SMBHSTADD);
size = ALI1535_QUICK;
outb_p(size, SMBHSTTYP);
break;
case I2C_SMBUS_BYTE:
outb_p(((addr & 0x7f) << 1) | (read_write & 0x01),
SMBHSTADD);
size = ALI1535_BYTE;
outb_p(size, SMBHSTTYP);
if (read_write == I2C_SMBUS_WRITE)
outb_p(command, SMBHSTCMD);
break;
case I2C_SMBUS_BYTE_DATA:
outb_p(((addr & 0x7f) << 1) | (read_write & 0x01),
SMBHSTADD);
size = ALI1535_BYTE_DATA;
outb_p(size, SMBHSTTYP);
outb_p(command, SMBHSTCMD);
if (read_write == I2C_SMBUS_WRITE)
outb_p(data->byte, SMBHSTDAT0);
break;
case I2C_SMBUS_WORD_DATA:
outb_p(((addr & 0x7f) << 1) | (read_write & 0x01),
SMBHSTADD);
size = ALI1535_WORD_DATA;
outb_p(size, SMBHSTTYP);
outb_p(command, SMBHSTCMD);
if (read_write == I2C_SMBUS_WRITE) {
outb_p(data->word & 0xff, SMBHSTDAT0);
outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1);
}
break;
case I2C_SMBUS_BLOCK_DATA:
outb_p(((addr & 0x7f) << 1) | (read_write & 0x01),
SMBHSTADD);
size = ALI1535_BLOCK_DATA;
outb_p(size, SMBHSTTYP);
outb_p(command, SMBHSTCMD);
if (read_write == I2C_SMBUS_WRITE) {
len = data->block[0];
if (len < 0) {
len = 0;
data->block[0] = len;
}
if (len > 32) {
len = 32;
data->block[0] = len;
}
outb_p(len, SMBHSTDAT0);
outb_p(inb_p(SMBHSTTYP) | ALI1535_BLOCK_CLR, SMBHSTTYP);
for (i = 1; i <= len; i++)
outb_p(data->block[i], SMBBLKDAT);
}
break;
default:
dev_warn(&adap->dev, "Unsupported transaction %d\n", size);
result = -EOPNOTSUPP;
goto EXIT;
}
result = ali1535_transaction(adap);
if (result)
goto EXIT;
if ((read_write == I2C_SMBUS_WRITE) || (size == ALI1535_QUICK)) {
result = 0;
goto EXIT;
}
switch (size) {
case ALI1535_BYTE:
data->byte = inb_p(SMBHSTDAT0);
break;
case ALI1535_BYTE_DATA:
data->byte = inb_p(SMBHSTDAT0);
break;
case ALI1535_WORD_DATA:
data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8);
break;
case ALI1535_BLOCK_DATA:
len = inb_p(SMBHSTDAT0);
if (len > 32)
len = 32;
data->block[0] = len;
outb_p(inb_p(SMBHSTTYP) | ALI1535_BLOCK_CLR, SMBHSTTYP);
for (i = 1; i <= data->block[0]; i++) {
data->block[i] = inb_p(SMBBLKDAT);
dev_dbg(&adap->dev, "Blk: len=%d, i=%d, data=%02x\n",
len, i, data->block[i]);
}
break;
}
EXIT:
return result;
}
static u32 ali1535_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 const struct i2c_algorithm smbus_algorithm = {
.smbus_xfer = ali1535_access,
.functionality = ali1535_func,
};
static struct i2c_adapter ali1535_adapter = {
.owner = THIS_MODULE,
.class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
.algo = &smbus_algorithm,
};
static const struct pci_device_id ali1535_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101) },
{ },
};
MODULE_DEVICE_TABLE(pci, ali1535_ids);
static int ali1535_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
if (ali1535_setup(dev)) {
dev_warn(&dev->dev,
"ALI1535 not detected, module not inserted.\n");
return -ENODEV;
}
ali1535_adapter.dev.parent = &dev->dev;
snprintf(ali1535_adapter.name, sizeof(ali1535_adapter.name),
"SMBus ALI1535 adapter at %04x", ali1535_offset);
return i2c_add_adapter(&ali1535_adapter);
}
static void ali1535_remove(struct pci_dev *dev)
{
i2c_del_adapter(&ali1535_adapter);
release_region(ali1535_smba, ALI1535_SMB_IOSIZE);
}
static struct pci_driver ali1535_driver = {
.name = "ali1535_smbus",
.id_table = ali1535_ids,
.probe = ali1535_probe,
.remove = ali1535_remove,
};
module_pci_driver(ali1535_driver);
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>");
MODULE_AUTHOR("Philip Edelbrock <phil@netroedge.com>");
MODULE_AUTHOR("Mark D. Studebaker <mdsxyz123@yahoo.com>");
MODULE_AUTHOR("Dan Eaton <dan.eaton@rocketlogix.com>");
MODULE_DESCRIPTION("ALI1535 SMBus driver");
MODULE_LICENSE("GPL"