cregit-Linux how code gets into the kernel

Release 4.11 drivers/i2c/busses/i2c-ali1563.c

/**
 *      i2c-ali1563.c - i2c driver for the ALi 1563 Southbridge
 *
 *      Copyright (C) 2004 Patrick Mochel
 *                    2005 Rudolf Marek <r.marek@assembler.cz>
 *
 *      The 1563 southbridge is deceptively similar to the 1533, with a
 *      few notable exceptions. One of those happens to be the fact they
 *      upgraded the i2c core to be 2.0 compliant, and happens to be almost
 *      identical to the i2c controller found in the Intel 801 south
 *      bridges.
 *
 *      This driver is based on a mix of the 15x3, 1535, and i801 drivers,
 *      with a little help from the ALi 1563 spec.
 *
 *      This file is released under the GPLv2
 */

#include <linux/module.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/pci.h>
#include <linux/acpi.h>


#define ALI1563_MAX_TIMEOUT	500

#define	ALI1563_SMBBA		0x80

#define ALI1563_SMB_IOEN	1

#define ALI1563_SMB_HOSTEN	2

#define ALI1563_SMB_IOSIZE	16


#define SMB_HST_STS	(ali1563_smba + 0)

#define SMB_HST_CNTL1	(ali1563_smba + 1)

#define SMB_HST_CNTL2	(ali1563_smba + 2)

#define SMB_HST_CMD	(ali1563_smba + 3)

#define SMB_HST_ADD	(ali1563_smba + 4)

#define SMB_HST_DAT0	(ali1563_smba + 5)

#define SMB_HST_DAT1	(ali1563_smba + 6)

#define SMB_BLK_DAT	(ali1563_smba + 7)


#define HST_STS_BUSY	0x01

#define HST_STS_INTR	0x02

#define HST_STS_DEVERR	0x04

#define HST_STS_BUSERR	0x08

#define HST_STS_FAIL	0x10

#define HST_STS_DONE	0x80

#define HST_STS_BAD	0x1c



#define HST_CNTL1_TIMEOUT	0x80

#define HST_CNTL1_LAST		0x40


#define HST_CNTL2_KILL		0x04

#define HST_CNTL2_START		0x40

#define HST_CNTL2_QUICK		0x00

#define HST_CNTL2_BYTE		0x01

#define HST_CNTL2_BYTE_DATA	0x02

#define HST_CNTL2_WORD_DATA	0x03

#define HST_CNTL2_BLOCK		0x05



#define HST_CNTL2_SIZEMASK	0x38


static struct pci_driver ali1563_pci_driver;

static unsigned short ali1563_smba;


static int ali1563_transaction(struct i2c_adapter *a, int size) { u32 data; int timeout; int status = -EIO; dev_dbg(&a->dev, "Transaction (pre): STS=%02x, CNTL1=%02x, " "CNTL2=%02x, CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMB_HST_STS), inb_p(SMB_HST_CNTL1), inb_p(SMB_HST_CNTL2), inb_p(SMB_HST_CMD), inb_p(SMB_HST_ADD), inb_p(SMB_HST_DAT0), inb_p(SMB_HST_DAT1)); data = inb_p(SMB_HST_STS); if (data & HST_STS_BAD) { dev_err(&a->dev, "ali1563: Trying to reset busy device\n"); outb_p(data | HST_STS_BAD, SMB_HST_STS); data = inb_p(SMB_HST_STS); if (data & HST_STS_BAD) return -EBUSY; } outb_p(inb_p(SMB_HST_CNTL2) | HST_CNTL2_START, SMB_HST_CNTL2); timeout = ALI1563_MAX_TIMEOUT; do { msleep(1); } while (((data = inb_p(SMB_HST_STS)) & HST_STS_BUSY) && --timeout); dev_dbg(&a->dev, "Transaction (post): STS=%02x, CNTL1=%02x, " "CNTL2=%02x, CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMB_HST_STS), inb_p(SMB_HST_CNTL1), inb_p(SMB_HST_CNTL2), inb_p(SMB_HST_CMD), inb_p(SMB_HST_ADD), inb_p(SMB_HST_DAT0), inb_p(SMB_HST_DAT1)); if (timeout && !(data & HST_STS_BAD)) return 0; if (!timeout) { dev_err(&a->dev, "Timeout - Trying to KILL transaction!\n"); /* Issue 'kill' to host controller */ outb_p(HST_CNTL2_KILL, SMB_HST_CNTL2); data = inb_p(SMB_HST_STS); status = -ETIMEDOUT; } /* device error - no response, ignore the autodetection case */ if (data & HST_STS_DEVERR) { if (size != HST_CNTL2_QUICK) dev_err(&a->dev, "Device error!\n"); status = -ENXIO; } /* bus collision */ if (data & HST_STS_BUSERR) { dev_err(&a->dev, "Bus collision!\n"); /* Issue timeout, hoping it helps */ outb_p(HST_CNTL1_TIMEOUT, SMB_HST_CNTL1); } if (data & HST_STS_FAIL) { dev_err(&a->dev, "Cleaning fail after KILL!\n"); outb_p(0x0, SMB_HST_CNTL2); } return status; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick Mochel24470.11%120.00%
Rudolf Marek8022.99%120.00%
David Brownell216.03%120.00%
Márton Németh20.57%120.00%
Greg Kroah-Hartman10.29%120.00%
Total348100.00%5100.00%


static int ali1563_block_start(struct i2c_adapter *a) { u32 data; int timeout; int status = -EIO; dev_dbg(&a->dev, "Block (pre): STS=%02x, CNTL1=%02x, " "CNTL2=%02x, CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMB_HST_STS), inb_p(SMB_HST_CNTL1), inb_p(SMB_HST_CNTL2), inb_p(SMB_HST_CMD), inb_p(SMB_HST_ADD), inb_p(SMB_HST_DAT0), inb_p(SMB_HST_DAT1)); data = inb_p(SMB_HST_STS); if (data & HST_STS_BAD) { dev_warn(&a->dev, "ali1563: Trying to reset busy device\n"); outb_p(data | HST_STS_BAD, SMB_HST_STS); data = inb_p(SMB_HST_STS); if (data & HST_STS_BAD) return -EBUSY; } /* Clear byte-ready bit */ outb_p(data | HST_STS_DONE, SMB_HST_STS); /* Start transaction and wait for byte-ready bit to be set */ outb_p(inb_p(SMB_HST_CNTL2) | HST_CNTL2_START, SMB_HST_CNTL2); timeout = ALI1563_MAX_TIMEOUT; do { msleep(1); } while (!((data = inb_p(SMB_HST_STS)) & HST_STS_DONE) && --timeout); dev_dbg(&a->dev, "Block (post): STS=%02x, CNTL1=%02x, " "CNTL2=%02x, CMD=%02x, ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMB_HST_STS), inb_p(SMB_HST_CNTL1), inb_p(SMB_HST_CNTL2), inb_p(SMB_HST_CMD), inb_p(SMB_HST_ADD), inb_p(SMB_HST_DAT0), inb_p(SMB_HST_DAT1)); if (timeout && !(data & HST_STS_BAD)) return 0; if (timeout == 0) status = -ETIMEDOUT; if (data & HST_STS_DEVERR) status = -ENXIO; dev_err(&a->dev, "SMBus Error: %s%s%s%s%s\n", timeout ? "" : "Timeout ", data & HST_STS_FAIL ? "Transaction Failed " : "", data & HST_STS_BUSERR ? "No response or Bus Collision " : "", data & HST_STS_DEVERR ? "Device Error " : "", !(data & HST_STS_DONE) ? "Transaction Never Finished " : ""); return status; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick Mochel27688.75%120.00%
David Brownell319.97%120.00%
Márton Németh20.64%120.00%
Greg Kroah-Hartman10.32%120.00%
Rudolf Marek10.32%120.00%
Total311100.00%5100.00%


static int ali1563_block(struct i2c_adapter *a, union i2c_smbus_data *data, u8 rw) { int i, len; int error = 0; /* Do we need this? */ outb_p(HST_CNTL1_LAST, SMB_HST_CNTL1); if (rw == I2C_SMBUS_WRITE) { len = data->block[0]; if (len < 1) len = 1; else if (len > 32) len = 32; outb_p(len, SMB_HST_DAT0); outb_p(data->block[1], SMB_BLK_DAT); } else len = 32; outb_p(inb_p(SMB_HST_CNTL2) | HST_CNTL2_BLOCK, SMB_HST_CNTL2); for (i = 0; i < len; i++) { if (rw == I2C_SMBUS_WRITE) { outb_p(data->block[i + 1], SMB_BLK_DAT); error = ali1563_block_start(a); if (error) break; } else { error = ali1563_block_start(a); if (error) break; if (i == 0) { len = inb_p(SMB_HST_DAT0); if (len < 1) len = 1; else if (len > 32) len = 32; } data->block[i+1] = inb_p(SMB_BLK_DAT); } } /* Do we need this? */ outb_p(HST_CNTL1_LAST, SMB_HST_CNTL1); return error; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick Mochel22896.61%150.00%
Richard Leitner83.39%150.00%
Total236100.00%2100.00%


static s32 ali1563_access(struct i2c_adapter *a, u16 addr, unsigned short flags, char rw, u8 cmd, int size, union i2c_smbus_data *data) { int error = 0; int timeout; u32 reg; for (timeout = ALI1563_MAX_TIMEOUT; timeout; timeout--) { reg = inb_p(SMB_HST_STS); if (!(reg & HST_STS_BUSY)) break; } if (!timeout) dev_warn(&a->dev, "SMBus not idle. HST_STS = %02x\n", reg); outb_p(0xff, SMB_HST_STS); /* Map the size to what the chip understands */ switch (size) { case I2C_SMBUS_QUICK: size = HST_CNTL2_QUICK; break; case I2C_SMBUS_BYTE: size = HST_CNTL2_BYTE; break; case I2C_SMBUS_BYTE_DATA: size = HST_CNTL2_BYTE_DATA; break; case I2C_SMBUS_WORD_DATA: size = HST_CNTL2_WORD_DATA; break; case I2C_SMBUS_BLOCK_DATA: size = HST_CNTL2_BLOCK; break; default: dev_warn(&a->dev, "Unsupported transaction %d\n", size); error = -EOPNOTSUPP; goto Done; } outb_p(((addr & 0x7f) << 1) | (rw & 0x01), SMB_HST_ADD); outb_p((inb_p(SMB_HST_CNTL2) & ~HST_CNTL2_SIZEMASK) | (size << 3), SMB_HST_CNTL2); /* Write the command register */ switch (size) { case HST_CNTL2_BYTE: if (rw == I2C_SMBUS_WRITE) /* Beware it uses DAT0 register and not CMD! */ outb_p(cmd, SMB_HST_DAT0); break; case HST_CNTL2_BYTE_DATA: outb_p(cmd, SMB_HST_CMD); if (rw == I2C_SMBUS_WRITE) outb_p(data->byte, SMB_HST_DAT0); break; case HST_CNTL2_WORD_DATA: outb_p(cmd, SMB_HST_CMD); if (rw == I2C_SMBUS_WRITE) { outb_p(data->word & 0xff, SMB_HST_DAT0); outb_p((data->word & 0xff00) >> 8, SMB_HST_DAT1); } break; case HST_CNTL2_BLOCK: outb_p(cmd, SMB_HST_CMD); error = ali1563_block(a, data, rw); goto Done; } error = ali1563_transaction(a, size); if (error) goto Done; if ((rw == I2C_SMBUS_WRITE) || (size == HST_CNTL2_QUICK)) goto Done; switch (size) { case HST_CNTL2_BYTE: /* Result put in SMBHSTDAT0 */ data->byte = inb_p(SMB_HST_DAT0); break; case HST_CNTL2_BYTE_DATA: data->byte = inb_p(SMB_HST_DAT0); break; case HST_CNTL2_WORD_DATA: data->word = inb_p(SMB_HST_DAT0) + (inb_p(SMB_HST_DAT1) << 8); break; } Done: return error; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick Mochel38090.48%125.00%
Jean Delvare215.00%125.00%
Richard Leitner102.38%125.00%
Rudolf Marek92.14%125.00%
Total420100.00%4100.00%


static u32 ali1563_func(struct i2c_adapter *a) { return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick Mochel22100.00%1100.00%
Total22100.00%1100.00%


static int ali1563_setup(struct pci_dev *dev) { u16 ctrl; pci_read_config_word(dev, ALI1563_SMBBA, &ctrl); /* SMB I/O Base in high 12 bits and must be aligned with the * size of the I/O space. */ ali1563_smba = ctrl & ~(ALI1563_SMB_IOSIZE - 1); if (!ali1563_smba) { dev_warn(&dev->dev, "ali1563_smba Uninitialized\n"); goto Err; } /* Check if device is enabled */ if (!(ctrl & ALI1563_SMB_HOSTEN)) { dev_warn(&dev->dev, "Host Controller not enabled\n"); goto Err; } if (!(ctrl & ALI1563_SMB_IOEN)) { dev_warn(&dev->dev, "I/O space not enabled, trying manually\n"); pci_write_config_word(dev, ALI1563_SMBBA, ctrl | ALI1563_SMB_IOEN); pci_read_config_word(dev, ALI1563_SMBBA, &ctrl); if (!(ctrl & ALI1563_SMB_IOEN)) { dev_err(&dev->dev, "I/O space still not enabled, giving up\n"); goto Err; } } if (acpi_check_region(ali1563_smba, ALI1563_SMB_IOSIZE, ali1563_pci_driver.name)) goto Err; if (!request_region(ali1563_smba, ALI1563_SMB_IOSIZE, ali1563_pci_driver.name)) { dev_err(&dev->dev, "Could not allocate I/O space at 0x%04x\n", ali1563_smba); goto Err; } dev_info(&dev->dev, "Found ALi1563 SMBus at 0x%04x\n", ali1563_smba); return 0; Err: return -ENODEV; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick Mochel13261.40%116.67%
Jean Delvare8238.14%466.67%
Richard Leitner10.47%116.67%
Total215100.00%6100.00%


static void ali1563_shutdown(struct pci_dev *dev) { release_region(ali1563_smba, ALI1563_SMB_IOSIZE); }

Contributors

PersonTokensPropCommitsCommitProp
Patrick Mochel18100.00%1100.00%
Total18100.00%1100.00%

static const struct i2c_algorithm ali1563_algorithm = { .smbus_xfer = ali1563_access, .functionality = ali1563_func, }; static struct i2c_adapter ali1563_adapter = { .owner = THIS_MODULE, .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, .algo = &ali1563_algorithm, };
static int ali1563_probe(struct pci_dev *dev, const struct pci_device_id *id_table) { int error; error = ali1563_setup(dev); if (error) goto exit; ali1563_adapter.dev.parent = &dev->dev; snprintf(ali1563_adapter.name, sizeof(ali1563_adapter.name), "SMBus ALi 1563 Adapter @ %04x", ali1563_smba); error = i2c_add_adapter(&ali1563_adapter); if (error) goto exit_shutdown; return 0; exit_shutdown: ali1563_shutdown(dev); exit: dev_warn(&dev->dev, "ALi1563 SMBus probe failed (%d)\n", error); return error; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick Mochel7066.67%125.00%
Jean Delvare2725.71%250.00%
Richard Leitner87.62%125.00%
Total105100.00%4100.00%


static void ali1563_remove(struct pci_dev *dev) { i2c_del_adapter(&ali1563_adapter); ali1563_shutdown(dev); }

Contributors

PersonTokensPropCommitsCommitProp
Patrick Mochel22100.00%1100.00%
Total22100.00%1100.00%

static const struct pci_device_id ali1563_id_table[] = { { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1563) }, {}, }; MODULE_DEVICE_TABLE(pci, ali1563_id_table); static struct pci_driver ali1563_pci_driver = { .name = "ali1563_smbus", .id_table = ali1563_id_table, .probe = ali1563_probe, .remove = ali1563_remove, }; module_pci_driver(ali1563_pci_driver); MODULE_LICENSE("GPL");

Overall Contributors

PersonTokensPropCommitsCommitProp
Patrick Mochel160582.31%29.52%
Jean Delvare1497.64%1152.38%
Rudolf Marek944.82%14.76%
David Brownell522.67%14.76%
Richard Leitner271.38%14.76%
Matthieu Castet70.36%14.76%
Jingoo Han60.31%14.76%
Greg Kroah-Hartman50.26%14.76%
Márton Németh40.21%14.76%
Axel Lin10.05%14.76%
Total1950100.00%21100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.