Re: [PATCH] i2c driver fixes for 2.6.0-test5

Greg KH
2003-09-23T00:26:57Z

ChangeSet 1.1315.1.3, 2003/09/15 16:15:33-07:00, greg@kroah.com

[PATCH] I2C: add the i2c-sis630 i2c bus driver

This is based on the lmsensor cvs version of the driver, but is cleaned
it up and ported it to 2.6.

drivers/i2c/busses/Kconfig | 16 +
drivers/i2c/busses/Makefile | 1
drivers/i2c/busses/i2c-sis630.c | 494 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 511 insertions(+)

diff -Nru a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
--- a/drivers/i2c/busses/Kconfig Mon Sep 22 16:15:46 2003
+++ b/drivers/i2c/busses/Kconfig Mon Sep 22 16:15:46 2003
@@ -166,6 +166,22 @@
in the lm_sensors package, which you can download at
http://www.lm-sensors.nu

+config I2C_SIS630
+ tristate " Sis630"
+ depends on I2C && PCI && EXPERIMENTAL
+ help
+ If you say yes to this option, support will be included for the
+ SiS630 SMBus (a subset of I2C) interface.
+
+ This can also be built as a module which can be inserted and removed
+ while the kernel is running. If you want to compile it as a module,
+ say M here and read <file:Documentation/modules.txt>.
+
+ The module will be called i2c-sis630.
+
+ You will also need the latest user-space utilties: you can find them
+ in the lm_sensors package, which you can download at
+ http://www.lm-sensors.nu

config I2C_SIS96X
tristate " SiS 96x"
diff -Nru a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
--- a/drivers/i2c/busses/Makefile Mon Sep 22 16:15:46 2003
+++ b/drivers/i2c/busses/Makefile Mon Sep 22 16:15:46 2003
@@ -11,5 +11,6 @@
obj-$(CONFIG_I2C_NFORCE2) += i2c-nforce2.o
obj-$(CONFIG_I2C_PIIX4) += i2c-piix4.o
obj-$(CONFIG_I2C_SIS5595) += i2c-sis5595.o
+obj-$(CONFIG_I2C_SIS630) += i2c-sis630.o
obj-$(CONFIG_I2C_SIS96X) += i2c-sis96x.o
obj-$(CONFIG_I2C_VIAPRO) += i2c-viapro.o
diff -Nru a/drivers/i2c/busses/i2c-sis630.c b/drivers/i2c/busses/i2c-sis630.c
--- /dev/null Wed Dec 31 16:00:00 1969
+++ b/drivers/i2c/busses/i2c-sis630.c Mon Sep 22 16:15:46 2003
@@ -0,0 +1,494 @@
+/*
+ i2c-sis630.c - Part of lm_sensors, Linux kernel modules for hardware
+ monitoring
+
+ Copyright (c) 2002,2003 Alexander Malysh <amalysh@web.de>
+
+ 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.
+*/
+
+/*
+ Changes:
+ 24.08.2002
+ Fixed the typo in sis630_access (Thanks to Mark M. Hoffman)
+ Changed sis630_transaction.(Thanks to Mark M. Hoffman)
+ 18.09.2002
+ Added SIS730 as supported
+ 21.09.2002
+ Added high_clock module option.If this option is set
+ used Host Master Clock 56KHz (default 14KHz).For now we are save old Host
+ Master Clock and after transaction completed restore (otherwise
+ it's confuse BIOS and hung Machine).
+ 24.09.2002
+ Fixed typo in sis630_access
+ Fixed logical error by restoring of Host Master Clock
+ 31.07.2003
+ Added block data read/write support.
+*/
+
+/*
+ Status: beta
+
+ Supports:
+ SIS 630
+ SIS 730
+
+ Note: we assume there can only be one device, with one SMBus interface.
+*/
+
+#define DEBUG
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+
+
+/* SIS630 SMBus registers */
+#define SMB_STS 0x80 /* status */
+#define SMB_EN 0x81 /* status enable */
+#define SMB_CNT 0x82
+#define SMBHOST_CNT 0x83
+#define SMB_ADDR 0x84
+#define SMB_CMD 0x85
+#define SMB_PCOUNT 0x86 /* processed count */
+#define SMB_COUNT 0x87
+#define SMB_BYTE 0x88 /* ~0x8F data byte field */
+#define SMBDEV_ADDR 0x90
+#define SMB_DB0 0x91
+#define SMB_DB1 0x92
+#define SMB_SAA 0x93
+
+/* register count for request_region */
+#define SIS630_SMB_IOREGION 20
+
+/* PCI address constants */
+/* acpi base address register */
+#define SIS630_ACPI_BASE_REG 0x74
+/* bios control register */
+#define SIS630_BIOS_CTL_REG 0x40
+
+/* Other settings */
+#define MAX_TIMEOUT 500
+
+/* SIS630 constants */
+#define SIS630_QUICK 0x00
+#define SIS630_BYTE 0x01
+#define SIS630_BYTE_DATA 0x02
+#define SIS630_WORD_DATA 0x03
+#define SIS630_PCALL 0x04
+#define SIS630_BLOCK_DATA 0x05
+
+/* insmod parameters */
+static int high_clock = 0;
+MODULE_PARM(high_clock, "i");
+MODULE_PARM_DESC(high_clock, "Set Host Master Clock to 56KHz (default 14KHz).");
+
+
+static unsigned short acpi_base = 0;
+
+static inline u8 sis630_read(u8 reg)
+{
+ return inb(acpi_base + reg);
+}
+
+static inline void sis630_write(u8 reg, u8 data)
+{
+ outb(data, acpi_base + reg);
+}
+
+static int sis630_transaction_start(struct i2c_adapter *adap, int size, u8 *oldclock)
+{
+ int temp;
+
+ /* Make sure the SMBus host is ready to start transmitting. */
+ if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) {
+ dev_dbg(&adap->dev, "SMBus busy (%02x).Resetting...\n",temp);
+ /* kill smbus transaction */
+ sis630_write(SMBHOST_CNT, 0x20);
+
+ if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) {
+ dev_dbg(&adap->dev, "Failed! (%02x)\n", temp);
+ return -1;
+ } else {
+ dev_dbg(&adap->dev, "Successfull!\n");
+ }
+ }
+
+ /* save old clock, so we can prevent machine for hung */
+ *oldclock = sis630_read(SMB_CNT);
+
+ dev_dbg(&adap->dev, "saved clock 0x%02x\n", *oldclock);
+
+ /* disable timeout interrupt , set Host Master Clock to 56KHz if requested */
+ if (high_clock > 0)
+ sis630_write(SMB_CNT, 0x20);
+ else
+ sis630_write(SMB_CNT, (*oldclock & ~0x40));
+
+ /* clear all sticky bits */
+ temp = sis630_read(SMB_STS);
+ sis630_write(SMB_STS, temp & 0x1e);
+
+ /* start the transaction by setting bit 4 and size */
+ sis630_write(SMBHOST_CNT,0x10 | (size & 0x07));
+
+ return 0;
+}
+
+static int sis630_transaction_wait(struct i2c_adapter *adap, int size)
+{
+ int temp, result = 0, timeout = 0;
+
+ /* We will always wait for a fraction of a second! */
+ do {
+ i2c_delay(1);
+ temp = sis630_read(SMB_STS);
+ /* check if block transmitted */
+ if (size == SIS630_BLOCK_DATA && (temp & 0x10))
+ break;
+ } while (!(temp & 0x0e) && (timeout++ < MAX_TIMEOUT));
+
+ /* If the SMBus is still busy, we give up */
+ if (timeout >= MAX_TIMEOUT) {
+ dev_dbg(&adap->dev, "SMBus Timeout!\n");
+ result = -1;
+ }
+
+ if (temp & 0x02) {
+ dev_dbg(&adap->dev, "Error: Failed bus transaction\n");
+ result = -1;
+ }
+
+ if (temp & 0x04) {
+ dev_err(&adap->dev, "Bus collision!\n");
+ result = -1;
+ /*
+ TBD: Datasheet say:
+ the software should clear this bit and restart SMBUS operation.
+ Should we do it or user start request again?
+ */
+ }
+
+ return result;
+}
+
+static void sis630_transaction_end(struct i2c_adapter *adap, u8 oldclock)
+{
+ int temp = 0;
+
+ /* clear all status "sticky" bits */
+ sis630_write(SMB_STS, temp);
+
+ dev_dbg(&adap->dev, "SMB_CNT before clock restore 0x%02x\n", sis630_read(SMB_CNT));
+
+ /*
+ * restore old Host Master Clock if high_clock is set
+ * and oldclock was not 56KHz
+ */
+ if (high_clock > 0 && !(oldclock & 0x20))
+ sis630_write(SMB_CNT,(sis630_read(SMB_CNT) & ~0x20));
+
+ dev_dbg(&adap->dev, "SMB_CNT after clock restore 0x%02x\n", sis630_read(SMB_CNT));
+}
+
+static int sis630_transaction(struct i2c_adapter *adap, int size)
+{
+ int result = 0;
+ u8 oldclock = 0;
+
+ result = sis630_transaction_start(adap, size, &oldclock);
+ if (!result) {
+ result = sis630_transaction_wait(adap, size);
+ sis630_transaction_end(adap, oldclock);
+ }
+
+ return result;
+}
+
+static int sis630_block_data(struct i2c_adapter *adap, union i2c_smbus_data *data, int read_write)
+{
+ int i, len = 0, rc = 0;
+ u8 oldclock = 0;
+
+ if (read_write == I2C_SMBUS_WRITE) {
+ len = data->block[0];
+ if (len < 0)
+ len = 0;
+ else if (len > 32)
+ len = 32;
+ sis630_write(SMB_COUNT, len);
+ for (i=1; i <= len; i++) {
+ dev_dbg(&adap->dev, "set data 0x%02x\n", data->block[i]);
+ /* set data */
+ sis630_write(SMB_BYTE+(i-1)%8, data->block[i]);
+ if (i==8 || (len<8 && i==len)) {
+ dev_dbg(&adap->dev, "start trans len=%d i=%d\n",len ,i);
+ /* first transaction */
+ if (sis630_transaction_start(adap, SIS630_BLOCK_DATA, &oldclock))
+ return -1;
+ }
+ else if ((i-1)%8 == 7 || i==len) {
+ dev_dbg(&adap->dev, "trans_wait len=%d i=%d\n",len,i);
+ if (i>8) {
+ dev_dbg(&adap->dev, "clear smbary_sts len=%d i=%d\n",len,i);
+ /*
+ If this is not first transaction,
+ we must clear sticky bit.
+ clear SMBARY_STS
+ */
+ sis630_write(SMB_STS,0x10);
+ }
+ if (sis630_transaction_wait(adap, SIS630_BLOCK_DATA)) {
+ dev_dbg(&adap->dev, "trans_wait failed\n");
+ rc = -1;
+ break;
+ }
+ }
+ }
+ }
+ else {
+ /* read request */
+ data->block[0] = len = 0;
+ if (sis630_transaction_start(adap, SIS630_BLOCK_DATA, &oldclock)) {
+ return -1;
+ }
+ do {
+ if (sis630_transaction_wait(adap, SIS630_BLOCK_DATA)) {
+ dev_dbg(&adap->dev, "trans_wait failed\n");
+ rc = -1;
+ break;
+ }
+ /* if this first transaction then read byte count */
+ if (len == 0)
+ data->block[0] = sis630_read(SMB_COUNT);
+
+ dev_dbg(&adap->dev, "block data read len=0x%x\n", data->block[0]);
+
+ for (i=0; i < 8 && len < data->block[0]; i++,len++) {
+ dev_dbg(&adap->dev, "read i=%d len=%d\n", i, len);
+ data->block[len+1] = sis630_read(SMB_BYTE+i);
+ }
+
+ dev_dbg(&adap->dev, "clear smbary_sts len=%d i=%d\n",len,i);
+
+ /* clear SMBARY_STS */
+ sis630_write(SMB_STS,0x10);
+ } while(len < data->block[0]);
+ }
+
+ sis630_transaction_end(adap, oldclock);
+
+ return rc;
+}
+
+/* Return -1 on error. */
+static s32 sis630_access(struct i2c_adapter *adap, u16 addr,
+ unsigned short flags, char read_write,
+ u8 command, int size, union i2c_smbus_data *data)
+{
+ switch (size) {
+ case I2C_SMBUS_QUICK:
+ sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
+ size = SIS630_QUICK;
+ break;
+ case I2C_SMBUS_BYTE:
+ sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
+ if (read_write == I2C_SMBUS_WRITE)
+ sis630_write(SMB_CMD, command);
+ size = SIS630_BYTE;
+ break;
+ case I2C_SMBUS_BYTE_DATA:
+ sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
+ sis630_write(SMB_CMD, command);
+ if (read_write == I2C_SMBUS_WRITE)
+ sis630_write(SMB_BYTE, data->byte);
+ size = SIS630_BYTE_DATA;
+ break;
+ case I2C_SMBUS_PROC_CALL:
+ case I2C_SMBUS_WORD_DATA:
+ sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01));
+ sis630_write(SMB_CMD, command);
+ if (read_write == I2C_SMBUS_WRITE) {
+ sis630_write(SMB_BYTE, data->word & 0xff);
+ sis630_write(SMB_BYTE + 1,(data->word & 0xff00) >> 8);
+ }
+ size = (size == I2C_SMBUS_PROC_CALL ? SIS630_PCALL : SIS630_WORD_DATA);
+ break;
+ case I2C_SMBUS_BLOCK_DATA:
+ sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01));
+ sis630_write(SMB_CMD, command);
+ size = SIS630_BLOCK_DATA;
+ return sis630_block_data(adap, data, read_write);
+ default:
+ printk("Unsupported I2C size\n");
+ return -1;
+ break;
+ }
+
+ if (sis630_transaction(adap, size))
+ return -1;
+
+ if ((size != SIS630_PCALL) &&
+ ((read_write == I2C_SMBUS_WRITE) || (size == SIS630_QUICK))) {
+ return 0;
+ }
+
+ switch(size) {
+ case SIS630_BYTE:
+ case SIS630_BYTE_DATA:
+ data->byte = sis630_read(SMB_BYTE);
+ break;
+ case SIS630_PCALL:
+ case SIS630_WORD_DATA:
+ data->word = sis630_read(SMB_BYTE) + (sis630_read(SMB_BYTE + 1) << 8);
+ break;
+ default:
+ return -1;
+ break;
+ }
+
+ return 0;
+}
+
+static u32 sis630_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_PROC_CALL |
+ I2C_FUNC_SMBUS_BLOCK_DATA;
+}
+
+static int sis630_setup(struct pci_dev *dummy)
+{
+ unsigned char b;
+ struct pci_dev *sis630_dev = NULL;
+ int retval = -ENODEV;
+
+ /* We need ISA bridge and not pci device passed in. */
+ sis630_dev = pci_get_device(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_503, sis630_dev);
+ if (!sis630_dev) {
+ dev_err(&dummy->dev, "Error: Can't detect 85C503/5513 ISA bridge!\n");
+ return -ENODEV;
+ }
+
+ /*
+ Enable ACPI first , so we can accsess reg 74-75
+ in acpi io space and read acpi base addr
+ */
+ if (!pci_read_config_byte(sis630_dev, SIS630_BIOS_CTL_REG,&b)) {
+ dev_err(&sis630_dev->dev, "Error: Can't read bios ctl reg\n");
+ goto exit;
+ }
+
+ /* if ACPI already enabled , do nothing */
+ if (!(b & 0x80) &&
+ !pci_write_config_byte(sis630_dev, SIS630_BIOS_CTL_REG, b | 0x80)) {
+ dev_err(&sis630_dev->dev, "Error: Can't enable ACPI\n");
+ goto exit;
+ }
+ /* Determine the ACPI base address */
+ if (!pci_read_config_word(sis630_dev,SIS630_ACPI_BASE_REG,&acpi_base)) {
+ dev_err(&sis630_dev->dev, "Error: Can't determine ACPI base address\n");
+ goto exit;
+ }
+
+ dev_dbg(&sis630_dev->dev, "ACPI base at 0x%04x\n", acpi_base);
+
+ /* Everything is happy, let's grab the memory and set things up. */
+ if (!request_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION, "sis630-smbus")) {
+ dev_err(&sis630_dev->dev, "SMBus registers 0x%04x-0x%04x already "
+ "in use!\n", acpi_base + SMB_STS, acpi_base + SMB_SAA);
+ goto exit;
+ }
+
+ retval = 0;
+
+exit:
+ pci_dev_put(sis630_dev);
+ return retval;
+}
+
+
+static struct i2c_algorithm smbus_algorithm = {
+ .name = "Non-I2C SMBus adapter",
+ .id = I2C_ALGO_SMBUS,
+ .smbus_xfer = sis630_access,
+ .functionality = sis630_func,
+};
+
+static struct i2c_adapter sis630_adapter = {
+ .owner = THIS_MODULE,
+ .name = "unset",
+ .algo = &smbus_algorithm,
+};
+
+static struct pci_device_id sis630_ids[] __devinitdata = {
+ { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_630) },
+ { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_730) },
+ { 0, }
+};
+
+static int __devinit sis630_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+
+ if (sis630_setup(dev)) {
+ dev_err(&dev->dev, "SIS630 comp. bus not detected, module not inserted.\n");
+ return -ENODEV;
+ }
+
+ /* set up the driverfs linkage to our parent device */
+ sis630_adapter.dev.parent = &dev->dev;
+
+ sprintf(sis630_adapter.name, "SMBus SIS630 adapter at %04x",
+ acpi_base + SMB_STS);
+
+ return i2c_add_adapter(&sis630_adapter);
+}
+
+static void __devexit sis630_remove(struct pci_dev *dev)
+{
+ i2c_del_adapter(&sis630_adapter);
+}
+
+
+static struct pci_driver sis630_driver = {
+ .name = "sis630 smbus",
+ .id_table = sis630_ids,
+ .probe = sis630_probe,
+ .remove = __devexit_p(sis630_remove),
+};
+
+static int __init i2c_sis630_init(void)
+{
+ printk("i2c-sis630.o version %s (%s)\n", I2C_VERSION, I2C_DATE);
+ return pci_module_init(&sis630_driver);
+}
+
+
+static void __exit i2c_sis630_exit(void)
+{
+ pci_unregister_driver(&sis630_driver);
+ release_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION);
+}
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alexander Malysh <amalysh@web.de>");
+MODULE_DESCRIPTION("SIS630 SMBus driver");
+
+module_init(i2c_sis630_init);
+module_exit(i2c_sis630_exit);