public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 3/3] i386: CS5535 chip support - SMBus
@ 2005-12-07 17:34 Ben Gardner
  2005-12-10 10:45 ` Andrew Morton
  2005-12-10 12:06 ` [lm-sensors] " Jean Delvare
  0 siblings, 2 replies; 3+ messages in thread
From: Ben Gardner @ 2005-12-07 17:34 UTC (permalink / raw)
  To: Andrew Morton; +Cc: lm-sensors, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 139 bytes --]

Provides a SMBus/I2C driver for the AMD CS5535, modeled after the
scx200_acb driver.

Signed-off-by: Ben Gardner <bgardner@wabtec.com>

[-- Attachment #2: cs5535-smb.patch.txt --]
[-- Type: text/plain, Size: 15085 bytes --]

 drivers/i2c/busses/Kconfig      |   11 
 drivers/i2c/busses/Makefile     |    1 
 drivers/i2c/busses/i2c-cs5535.c |  553 ++++++++++++++++++++++++++++++++++++++++
 include/linux/i2c-id.h          |    1 
 4 files changed, 566 insertions(+)

Index: linux-2.6.14/drivers/i2c/busses/Makefile
===================================================================
--- linux-2.6.14.orig/drivers/i2c/busses/Makefile
+++ linux-2.6.14/drivers/i2c/busses/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_I2C_AMD756)	+= i2c-amd756.o
 obj-$(CONFIG_I2C_AMD756_S4882)	+= i2c-amd756-s4882.o
 obj-$(CONFIG_I2C_AMD8111)	+= i2c-amd8111.o
 obj-$(CONFIG_I2C_AU1550)	+= i2c-au1550.o
+obj-$(CONFIG_I2C_CS5535)	+= i2c-cs5535.o
 obj-$(CONFIG_I2C_ELEKTOR)	+= i2c-elektor.o
 obj-$(CONFIG_I2C_HYDRA)		+= i2c-hydra.o
 obj-$(CONFIG_I2C_I801)		+= i2c-i801.o
Index: linux-2.6.14/drivers/i2c/busses/Kconfig
===================================================================
--- linux-2.6.14.orig/drivers/i2c/busses/Kconfig
+++ linux-2.6.14/drivers/i2c/busses/Kconfig
@@ -84,6 +84,17 @@ config I2C_AU1550
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-au1550.
 
+config I2C_CS5535
+	tristate "AMD CS5535 SMBus (Geode GX)"
+	depends on I2C && CS5535 && EXPERIMENTAL
+	help
+	  Enable the use of the SMB controller on the CS5535 companion device.
+
+	  If you don't know what to do here, say N.
+
+	  This support is also available as a module.  If so, the module
+	  will be called i2c-cs5535.
+
 config I2C_ELEKTOR
 	tristate "Elektor ISA card"
 	depends on I2C && ISA && BROKEN_ON_SMP
Index: linux-2.6.14/drivers/i2c/busses/i2c-cs5535.c
===================================================================
--- /dev/null
+++ linux-2.6.14/drivers/i2c/busses/i2c-cs5535.c
@@ -0,0 +1,553 @@
+/*  linux/drivers/i2c/i2c-cs5535.c
+ *
+ *  Copyright (c) 2005 Ben Gardner <bgardner@wabtec.com>
+ *
+ *  AMD CS5535 SMB support - mostly identical to
+ *  National Semiconductor SCx200 ACCESS.bus support
+ *  except for the detection routine.
+ *
+ *  Based on scx200_acb.c which is:
+ *      Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.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; version 2 of the License.
+ *
+ *  TODO: the Access.Bus logic should be put in a separate file, as it could
+ *        be shared with the scx200.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/smp_lock.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <asm/msr.h>
+#include <asm/io.h>
+
+#define NAME			"cs5535_smb"
+
+MODULE_AUTHOR("Ben Gardner <bgardner@wabtec.com>");
+MODULE_DESCRIPTION("AMD CS5535 SMB Driver");
+MODULE_LICENSE("GPL");
+
+/* Needed to see if the cs5535 is present */
+extern u32 cs5535_gpio_base;
+
+#define MSR_LBAR_SMB		0x5140000B
+#define SMB_IO_SIZE		8
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(x...) printk(KERN_DEBUG NAME ": " x)
+#else
+#define DBG(x...)
+#endif
+
+/* The hardware supports interrupt driven mode too, but I haven't
+   implemented that. */
+#define POLL_TIMEOUT (HZ)
+
+enum cs5535_smb_state {
+	state_idle,
+	state_address,
+	state_command,
+	state_repeat_start,
+	state_quick,
+	state_read,
+	state_write,
+};
+
+static const char *cs5535_smb_state_name[] = {
+	"idle",
+	"address",
+	"command",
+	"repeat_start",
+	"quick",
+	"read",
+	"write",
+};
+
+/* Physical interface */
+struct cs5535_smb_iface {
+	struct i2c_adapter	adapter;
+	u32			base;
+	struct semaphore	sem;
+
+	/* State machine data */
+	enum cs5535_smb_state	state;
+	int			result;
+	u8			address_byte;
+	u8			command;
+	u8			*ptr;
+	char			needs_reset;
+	u32			len;
+};
+
+
+/* Register Definitions */
+#define SMBSDA		(iface->base + 0)
+#define SMBST		(iface->base + 1)
+#define    SMBST_SDAST		0x40	/* SDA Status */
+#define    SMBST_BER		0x20
+#define    SMBST_NEGACK		0x10	/* Negative Acknowledge */
+#define    SMBST_STASTR		0x08	/* Stall After Start */
+#define    SMBST_MASTER		0x02
+#define SMBCST		(iface->base + 2)
+#define    SMBCST_BB		0x02
+#define SMBCTL1		(iface->base + 3)
+#define    SMBCTL1_STASTRE	0x80
+#define    SMBCTL1_NMINTE	0x40
+#define    SMBCTL1_ACK		0x10
+#define    SMBCTL1_INTEN	0x04
+#define    SMBCTL1_STOP		0x02
+#define    SMBCTL1_START	0x01
+#define SMBADDR		(iface->base + 4)
+#define SMBCTL2		(iface->base + 5)
+#define    SMBCTL2_ENABLE	0x01
+
+/************************************************************************/
+
+static u8 smb_inb(u32 p)
+{
+	u8 ch = inb(p);
+	udelay(5);
+	return ch;
+}
+
+static void smb_outb(u8 ch, u32 p)
+{
+	outb(ch, p);
+	udelay(5);
+}
+
+static void cs5535_smb_machine(struct cs5535_smb_iface *iface, u8 status)
+{
+	const char *errmsg;
+
+	DBG("state %s, status = 0x%02x\n",
+	    cs5535_smb_state_name[iface->state], status);
+
+	if (status & SMBST_BER) {
+		errmsg = "bus error";
+		goto error;
+	}
+	if (!(status & SMBST_MASTER)) {
+		errmsg = "not master";
+		goto error;
+	}
+	if (status & SMBST_NEGACK) {
+		DBG("negative acknowledge in state %s\n",
+		    cs5535_smb_state_name[iface->state]);
+
+		iface->state = state_idle;
+		iface->result = -ENXIO;
+
+		smb_outb(smb_inb(SMBCTL1) | SMBCTL1_STOP, SMBCTL1);
+		smb_outb(SMBST_STASTR | SMBST_NEGACK, SMBST);
+		smb_outb(0, SMBST); /* Status Reg bug workaround */
+		return;
+	}
+
+	switch (iface->state) {
+	case state_idle:
+		dev_warn(&iface->adapter.dev, "interrupt in idle state\n");
+		break;
+
+	case state_address:
+		/* Do a pointer write first */
+		smb_outb(iface->address_byte & ~1, SMBSDA);
+
+		iface->state = state_command;
+		break;
+
+	case state_command:
+		smb_outb(iface->command, SMBSDA);
+
+		if (iface->address_byte & 1)
+			iface->state = state_repeat_start;
+		else
+			iface->state = state_write;
+		break;
+
+	case state_repeat_start:
+		smb_outb(smb_inb(SMBCTL1) | SMBCTL1_START, SMBCTL1);
+		/* fallthrough */
+
+	case state_quick:
+		if (iface->address_byte & 1) {
+			if (iface->len == 1)
+				smb_outb(smb_inb(SMBCTL1) | SMBCTL1_ACK,
+					 SMBCTL1);
+			else
+				smb_outb(smb_inb(SMBCTL1) & ~SMBCTL1_ACK,
+					 SMBCTL1);
+			smb_outb(iface->address_byte, SMBSDA);
+
+			iface->state = state_read;
+		} else {
+			smb_outb(iface->address_byte, SMBSDA);
+
+			iface->state = state_write;
+		}
+		break;
+
+	case state_read:
+		/* Set ACK if receiving the last byte */
+		if (iface->len == 1)
+			smb_outb(smb_inb(SMBCTL1) | SMBCTL1_ACK, SMBCTL1);
+		else
+			smb_outb(smb_inb(SMBCTL1) & ~SMBCTL1_ACK, SMBCTL1);
+
+		*iface->ptr++ = smb_inb(SMBSDA);
+		--iface->len;
+
+		if (iface->len == 0) {
+			iface->result = 0;
+			iface->state = state_idle;
+			smb_outb(smb_inb(SMBCTL1) | SMBCTL1_STOP, SMBCTL1);
+		}
+
+		break;
+
+	case state_write:
+		if (iface->len == 0) {
+			iface->result = 0;
+			iface->state = state_idle;
+			smb_outb(smb_inb(SMBCTL1) | SMBCTL1_STOP, SMBCTL1);
+			break;
+		}
+
+		smb_outb(*iface->ptr++, SMBSDA);
+		--iface->len;
+
+		break;
+	}
+
+	return;
+
+error:
+	dev_err(&iface->adapter.dev, "%s in state %s\n", errmsg,
+		cs5535_smb_state_name[iface->state]);
+
+	iface->state = state_idle;
+	iface->result = -EIO;
+	iface->needs_reset = 1;
+}
+
+static void cs5535_smb_timeout(struct cs5535_smb_iface *iface)
+{
+	dev_err(&iface->adapter.dev, "timeout in state %s\n",
+		cs5535_smb_state_name[iface->state]);
+
+	iface->state = state_idle;
+	iface->result = -EIO;
+	iface->needs_reset = 1;
+}
+
+static void cs5535_smb_poll(struct cs5535_smb_iface *iface)
+{
+	u8 status = 0;
+	unsigned long timeout;
+
+	timeout = jiffies + POLL_TIMEOUT;
+	while (time_before(jiffies, timeout)) {
+
+		/* The i2c bus takes 9us per bit, 10 bits per transaction.
+		 * This amounts to ~100us per char. Since that time is quite
+		 * small and we can wait longer, we'll just yield.
+		 */
+		yield();
+
+		status = smb_inb(SMBST);
+		if ((status & (SMBST_SDAST | SMBST_BER | SMBST_NEGACK)) != 0) {
+			cs5535_smb_machine(iface, status);
+			return;
+		}
+	}
+
+	cs5535_smb_timeout(iface);
+}
+
+static void cs5535_smb_reset(struct cs5535_smb_iface *iface)
+{
+	/* Disable the ACCESS.bus device and Configure the SCL
+	 * frequency: 16 clock cycles */
+
+	smb_outb(0x70, SMBCTL2); /* clock time is 4.7 us */
+	/* interrupt mode */
+	smb_outb(SMBCTL1_INTEN, SMBCTL1);
+	/* Disable slave address */
+	smb_outb(0, SMBADDR);
+	/* Enable the ACCESS.bus device */
+	smb_outb(smb_inb(SMBCTL2) | SMBCTL2_ENABLE, SMBCTL2);
+	/* Free STALL after START */
+	smb_outb(smb_inb(SMBCTL1) & ~(SMBCTL1_STASTRE | SMBCTL1_NMINTE), SMBCTL1);
+	/* Send a STOP */
+	smb_outb(smb_inb(SMBCTL1) | SMBCTL1_STOP, SMBCTL1);
+	/* Clear BER, NEGACK and STASTR bits */
+	smb_outb(SMBST_BER | SMBST_NEGACK | SMBST_STASTR, SMBST);
+	smb_outb(0, SMBST); /* Status Reg bug workaround */
+
+	/* Clear BB bit */
+	smb_outb(smb_inb(SMBCST) | SMBCST_BB, SMBCST);
+}
+
+static s32 cs5535_smb_smbus_xfer(struct i2c_adapter *adapter,
+				 u16 address, unsigned short flags,
+				 char rw, u8 command, int size,
+				 union i2c_smbus_data *data)
+{
+	struct cs5535_smb_iface *iface = i2c_get_adapdata(adapter);
+	int len;
+	u8 *buffer;
+	u16 cur_word;
+	int rc;
+
+	switch (size) {
+	case I2C_SMBUS_QUICK:
+		len = 0;
+		buffer = NULL;
+		break;
+
+	case I2C_SMBUS_BYTE:
+		if (rw == I2C_SMBUS_READ) {
+			len = 1;
+			buffer = &data->byte;
+		} else {
+			len = 1;
+			buffer = &command;
+		}
+		break;
+
+	case I2C_SMBUS_BYTE_DATA:
+		len = 1;
+		buffer = &data->byte;
+		break;
+
+	case I2C_SMBUS_WORD_DATA:
+		len = 2;
+		cur_word = cpu_to_le16(data->word);
+		buffer = (u8 *)&cur_word;
+		break;
+
+	case I2C_SMBUS_BLOCK_DATA:
+		len = data->block[0];
+		buffer = &data->block[1];
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	DBG("size=%d, address=0x%x, command=0x%x, len=%d, read=%d\n",
+	    size, address, command, len, rw == I2C_SMBUS_READ);
+
+	if (!len && rw == I2C_SMBUS_READ) {
+		dev_warn(&adapter->dev, "zero length read\n");
+		return -EINVAL;
+	}
+
+	if (len && !buffer) {
+		dev_warn(&adapter->dev, "nonzero length but no buffer\n");
+		return -EFAULT;
+	}
+
+	down(&iface->sem);
+
+	iface->address_byte = address << 1;
+	if (rw == I2C_SMBUS_READ)
+		iface->address_byte |= 1;
+	iface->command = command;
+	iface->ptr = buffer;
+	iface->len = len;
+	iface->result = -EINVAL;
+	iface->needs_reset = 0;
+
+	smb_outb(smb_inb(SMBCTL1) | SMBCTL1_START, SMBCTL1);
+
+	if (size == I2C_SMBUS_QUICK || size == I2C_SMBUS_BYTE)
+		iface->state = state_quick;
+	else
+		iface->state = state_address;
+
+	while (iface->state != state_idle)
+		cs5535_smb_poll(iface);
+
+	if (iface->needs_reset)
+		cs5535_smb_reset(iface);
+
+	rc = iface->result;
+
+	up(&iface->sem);
+
+	if (rc == 0 && size == I2C_SMBUS_WORD_DATA && rw == I2C_SMBUS_READ)
+		data->word = le16_to_cpu(cur_word);
+
+#ifdef DEBUG
+	DBG(": transfer done, result: %d", rc);
+	if (buffer) {
+		int i;
+		printk(" data:");
+		for (i = 0; i < len; ++i)
+			printk(" %02x", buffer[i]);
+	}
+	printk("\n");
+#endif
+
+	return rc;
+}
+
+static u32 cs5535_smb_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 cs5535_smb_algorithm = {
+	.smbus_xfer	= cs5535_smb_smbus_xfer,
+	.functionality	= cs5535_smb_func,
+};
+static struct cs5535_smb_iface *cs5535_iface;
+
+static int cs5535_smb_probe(struct cs5535_smb_iface *iface)
+{
+	u8 val;
+
+	cs5535_smb_reset(iface);
+
+	/* Disable the ACCESS.bus device and Configure the SCL
+	 * frequency: 16 clock cycles */
+	smb_outb(0x70, SMBCTL2);
+	if (smb_inb(SMBCTL2) != 0x70) {
+		DBG("SMBCTL2 readback failed\n");
+		return -ENXIO;
+	}
+
+	smb_outb(smb_inb(SMBCTL1) | SMBCTL1_NMINTE, SMBCTL1);
+
+	val = smb_inb(SMBCTL1);
+	if (val) {
+		DBG("disabled, but SMBCTL1=0x%02x\n", val);
+		return -ENXIO;
+	}
+
+	smb_outb(smb_inb(SMBCTL2) | SMBCTL2_ENABLE, SMBCTL2);
+
+	smb_outb(smb_inb(SMBCTL1) | SMBCTL1_NMINTE, SMBCTL1);
+
+	val = smb_inb(SMBCTL1);
+	if ((val & SMBCTL1_NMINTE) != SMBCTL1_NMINTE) {
+		DBG("enabled, but NMINTE won't be set, SMBCTL1=0x%02x\n", val);
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+static int __init cs5535_smb_create(int base, int index)
+{
+	struct cs5535_smb_iface *iface;
+	struct i2c_adapter *adapter;
+	int rc = 0;
+
+	iface = kzalloc(sizeof(*iface), GFP_KERNEL);
+	if (!iface) {
+		printk(KERN_ERR NAME ": can't allocate memory\n");
+		rc = -ENOMEM;
+		goto errout;
+	}
+
+	adapter = &iface->adapter;
+	i2c_set_adapdata(adapter, iface);
+	snprintf(adapter->name, I2C_NAME_SIZE, "CS5535 SMB%d", index);
+	adapter->owner = THIS_MODULE;
+	adapter->id = I2C_HW_SMBUS_CS5535;
+	adapter->algo = &cs5535_smb_algorithm;
+	adapter->class = I2C_CLASS_HWMON;
+
+	init_MUTEX(&iface->sem);
+
+	iface->base = base;
+	if (request_region(iface->base, SMB_IO_SIZE, adapter->name) == 0) {
+		printk(KERN_ERR NAME ": request_region(%d) failed\n",
+		       iface->base);
+		rc = -EBUSY;
+		goto errout;
+	}
+
+	rc = cs5535_smb_probe(iface);
+	if (rc) {
+		dev_warn(&adapter->dev, "probe failed\n");
+		goto errout;
+	}
+
+	cs5535_smb_reset(iface);
+
+	if (i2c_add_adapter(adapter) < 0) {
+		dev_err(&adapter->dev, "failed to register\n");
+		rc = -ENODEV;
+		goto errout;
+	}
+
+	cs5535_iface = iface;
+
+	return 0;
+
+errout:
+	if (iface) {
+		if (iface->base)
+			release_region(iface->base, SMB_IO_SIZE);
+		kfree(iface);
+	}
+	return rc;
+}
+
+static int __init cs5535_smb_init(void)
+{
+	u32 low32, high32;
+	u32 smb_base;
+
+	pr_debug(NAME ": AMD CS5535 SMB Driver\n");
+
+	if (cs5535_gpio_base == 0) {
+		printk(KERN_WARNING NAME ": CS5535 GPIO not present\n");
+		return -ENODEV;
+	}
+
+	/* Grab & reserve the SMB I/O range */
+	rdmsr(MSR_LBAR_SMB, low32, high32);
+
+	/* Check the mask and whether SMB is enabled */
+	if (high32 != 0x0000F001) {
+		/* TODO: enable SMB IO mappings via LBAR? */
+		printk(KERN_WARNING NAME ": SMBus not enabled\n");
+		return -ENODEV;
+	}
+
+	/* SMBus IO size is 8 bytes */
+	smb_base = low32 & 0x0000FFF8;
+
+	return cs5535_smb_create(smb_base, 0);
+}
+
+static void __exit cs5535_smb_cleanup(void)
+{
+	if (cs5535_iface != NULL) {
+		release_region(cs5535_iface->base, SMB_IO_SIZE);
+		i2c_del_adapter(&cs5535_iface->adapter);
+		kfree(cs5535_iface);
+	}
+}
+
+module_init(cs5535_smb_init);
+module_exit(cs5535_smb_cleanup);
+
Index: linux-2.6.14/include/linux/i2c-id.h
===================================================================
--- linux-2.6.14.orig/include/linux/i2c-id.h
+++ linux-2.6.14/include/linux/i2c-id.h
@@ -256,6 +256,7 @@
 #define I2C_HW_SMBUS_OV518	0x04000f /* OV518(+) USB 1.1 webcam ICs */
 #define I2C_HW_SMBUS_OV519	0x040010 /* OV519 USB 1.1 webcam IC */
 #define I2C_HW_SMBUS_OVFX2	0x040011 /* Cypress/OmniVision FX2 webcam */
+#define I2C_HW_SMBUS_CS5535	0x040012
 
 /* --- ISA pseudo-adapter						*/
 #define I2C_HW_ISA		0x050000

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH 3/3] i386: CS5535 chip support - SMBus
  2005-12-07 17:34 [PATCH 3/3] i386: CS5535 chip support - SMBus Ben Gardner
@ 2005-12-10 10:45 ` Andrew Morton
  2005-12-10 12:06 ` [lm-sensors] " Jean Delvare
  1 sibling, 0 replies; 3+ messages in thread
From: Andrew Morton @ 2005-12-10 10:45 UTC (permalink / raw)
  To: Ben Gardner; +Cc: lm-sensors, linux-kernel


Module symbol problems with `make allmodconfig':

*** Warning: "cs5535_gpio_base" [drivers/i2c/busses/i2c-cs5535.ko] undefined!
*** Warning: "cs5535_gpio_mask" [drivers/char/cs5535_gpio.ko] undefined!
*** Warning: "cs5535_gpio_base" [drivers/char/cs5535_gpio.ko] undefined!

Perhaps CONFIG_CS5535=m wasn't supposed to be possible?

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [lm-sensors] [PATCH 3/3] i386: CS5535 chip support - SMBus
  2005-12-07 17:34 [PATCH 3/3] i386: CS5535 chip support - SMBus Ben Gardner
  2005-12-10 10:45 ` Andrew Morton
@ 2005-12-10 12:06 ` Jean Delvare
  1 sibling, 0 replies; 3+ messages in thread
From: Jean Delvare @ 2005-12-10 12:06 UTC (permalink / raw)
  To: Ben Gardner; +Cc: Andrew Morton, LKML, LM Sensors

Hi Ben,

> Index: linux-2.6.14/drivers/i2c/busses/Makefile
> ===================================================================

If you are using quilt, please set QUILT_NO_DIFF_INDEX=1.

> --- linux-2.6.14.orig/drivers/i2c/busses/Kconfig
> +++ linux-2.6.14/drivers/i2c/busses/Kconfig
> @@ -84,6 +84,17 @@ config I2C_AU1550
>  	  This driver can also be built as a module.  If so, the module
>  	  will be called i2c-au1550.
>  
> +config I2C_CS5535
> +	tristate "AMD CS5535 SMBus (Geode GX)"
> +	depends on I2C && CS5535 && EXPERIMENTAL
> +	help
> +	  Enable the use of the SMB controller on the CS5535 companion device.

Please use "SMBus" rather than "SMB" which is something different.

> --- /dev/null
> +++ linux-2.6.14/drivers/i2c/busses/i2c-cs5535.c
> @@ -0,0 +1,553 @@
> +/*  linux/drivers/i2c/i2c-cs5535.c

Redundant.

> + *
> + *  Copyright (c) 2005 Ben Gardner <bgardner@wabtec.com>
> + *
> + *  AMD CS5535 SMB support - mostly identical to

SMBus.

> + *  National Semiconductor SCx200 ACCESS.bus support
> + *  except for the detection routine.
> + *
> + *  Based on scx200_acb.c which is:
> + *      Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>

Why not extend that driver then, rather than having a separate one?

> +#include <linux/config.h>

This file should no more be included explicitely.

> +#include <linux/pci.h>

Why would you need this?

> +#include <linux/interrupt.h>

And this?

> +MODULE_DESCRIPTION("AMD CS5535 SMB Driver");

SMBus :)

> +/* Needed to see if the cs5535 is present */
> +extern u32 cs5535_gpio_base;

Move this to a header file.

> +#undef DEBUG
> +
> +#ifdef DEBUG
> +#define DBG(x...) printk(KERN_DEBUG NAME ": " x)
> +#else
> +#define DBG(x...)
> +#endif

No. We have a global configuration flag which enables debugging on i2c
bus drivers (CONFIG_I2C_DEBUG_BUS). When enabled, DEBUG will be set for
you. Then, use dev_dbg (preferably) and pr_debug.

> +static void cs5535_smb_timeout(struct cs5535_smb_iface *iface)
> +{
> +	dev_err(&iface->adapter.dev, "timeout in state %s\n",
> +		cs5535_smb_state_name[iface->state]);
> +
> +	iface->state = state_idle;
> +	iface->result = -EIO;
> +	iface->needs_reset = 1;
> +}

I see relatively little interest in having a separate function for
this, when this really only is the error path of function
cs5535_smb_poll below.

> +static void cs5535_smb_poll(struct cs5535_smb_iface *iface)
> +{
> +	u8 status = 0;

Useless initialization.

> +	unsigned long timeout;
> +
> +	timeout = jiffies + POLL_TIMEOUT;
> +	while (time_before(jiffies, timeout)) {
> +

No blank line at beginning of blocks please.

> +		/* The i2c bus takes 9us per bit, 10 bits per transaction.
> +		 * This amounts to ~100us per char. Since that time is quite
> +		 * small and we can wait longer, we'll just yield.
> +		 */

Where do these figures come from? At 100kHz that would be 10us per bit,
and it's more like 9 bits per byte transfered. The bit count of a
transaction obviously depends on the transaction type (length), and can
be generally expressed as 2+9*N where N is the number of transferred
bytes (counting the address).

It's always approximate anyway as start and stop conditions are not
real bits, additional delays can occur between bytes, and slaves are
free to slow down the clock if they want to.

> +		yield();
> +
> +		status = smb_inb(SMBST);
> +		if ((status & (SMBST_SDAST | SMBST_BER | SMBST_NEGACK)) != 0) {
> +			cs5535_smb_machine(iface, status);
> +			return;
> +		}
> +	}
> +
> +	cs5535_smb_timeout(iface);
> +}

> +static s32 cs5535_smb_smbus_xfer(struct i2c_adapter *adapter,
> +				 u16 address, unsigned short flags,
> +				 char rw, u8 command, int size,
> +				 union i2c_smbus_data *data)
> +{
> (...)
> +	case I2C_SMBUS_BYTE:
> +		if (rw == I2C_SMBUS_READ) {
> +			len = 1;
> +			buffer = &data->byte;
> +		} else {
> +			len = 1;
> +			buffer = &command;
> +		}
> +		break;

You can refactor "len = 1".

> +	DBG("size=%d, address=0x%x, command=0x%x, len=%d, read=%d\n",
> +	    size, address, command, len, rw == I2C_SMBUS_READ);

rw == I2C_SMBUS_READ can be simplified to just rw.

> +	if (!len && rw == I2C_SMBUS_READ) {
> +		dev_warn(&adapter->dev, "zero length read\n");
> +		return -EINVAL;
> +	}
> +
> +	if (len && !buffer) {
> +		dev_warn(&adapter->dev, "nonzero length but no buffer\n");
> +		return -EFAULT;
> +	}

These would be internal driver errors, right? If so, dev_warn is not
appropriate. I'd make these dependent upon DEBUG, and use dev_dbg.

> +	iface->address_byte = address << 1;
> +	if (rw == I2C_SMBUS_READ)
> +		iface->address_byte |= 1;

Can be simplified to:
	iface->address_byte = (address << 1) | rw;

> +	iface->command = command;
> +	iface->ptr = buffer;
> +	iface->len = len;
> +	iface->result = -EINVAL;
> +	iface->needs_reset = 0;

Wouldn't it be more efficient to have cs5535_smb_reset reset that flag
rather that doing it on each and every transaction?

> +static int __init cs5535_smb_create(int base, int index)
> +{
> +	struct cs5535_smb_iface *iface;
> +	struct i2c_adapter *adapter;
> +	int rc = 0;

Useless initialization.

> +	if (request_region(iface->base, SMB_IO_SIZE, adapter->name) == 0) {

if (!request_region(...)) {

> +	rc = cs5535_smb_probe(iface);
> +	if (rc) {
> +		dev_warn(&adapter->dev, "probe failed\n");
> +		goto errout;
> +	}

You can't use dev_warn() before the adapter is added. adapter->dev is
not defined at this point!

> +
> +	cs5535_smb_reset(iface);
> +
> +	if (i2c_add_adapter(adapter) < 0) {
> +		dev_err(&adapter->dev, "failed to register\n");
> +		rc = -ENODEV;
> +		goto errout;
> +	}

Ditto.

> +errout:
> +	if (iface) {
> +		if (iface->base)
> +			release_region(iface->base, SMB_IO_SIZE);
> +		kfree(iface);
> +	}
> +	return rc;
> +}

Please define more labels so that you do not have to test anything in
the cleanup path. While doing so, you'll probably notice that your
current cleanup code is not correct (if request_region fails you still
call release_region).

> +static int __init cs5535_smb_init(void)
> +{
> (...)
> +	return cs5535_smb_create(smb_base, 0);
> +}

Why did you partly setup the driver architecture so that you could
support several devices, and finally don't do it? It wouldn't work
properly anyway, as you have a single slot to store the interface
pointer. So maybe it would make more sense to simplify the code and
explicitely only support one device.

> +static void __exit cs5535_smb_cleanup(void)
> +{
> +	if (cs5535_iface != NULL) {
> +		release_region(cs5535_iface->base, SMB_IO_SIZE);
> +		i2c_del_adapter(&cs5535_iface->adapter);
> +		kfree(cs5535_iface);
> +	}
> +}

How could cs5535_iface be NULL at this point? Looks like a useless test
to me. You are also not cleaning up in the proper order, bus
deregistration should happen before region release, else you have
(theoretical) room for access to a no more reserved region.

> +
> +module_init(cs5535_smb_init);
> +module_exit(cs5535_smb_cleanup);
> +

No blank line at end of file please.

> --- linux-2.6.14.orig/include/linux/i2c-id.h
> +++ linux-2.6.14/include/linux/i2c-id.h
> @@ -256,6 +256,7 @@
>  #define I2C_HW_SMBUS_OV518	0x04000f /* OV518(+) USB 1.1 webcam ICs */
>  #define I2C_HW_SMBUS_OV519	0x040010 /* OV519 USB 1.1 webcam IC */
>  #define I2C_HW_SMBUS_OVFX2	0x040011 /* Cypress/OmniVision FX2 webcam */
> +#define I2C_HW_SMBUS_CS5535	0x040012

Some comment would be welcome.

Thanks,
-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2005-12-10 12:04 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-12-07 17:34 [PATCH 3/3] i386: CS5535 chip support - SMBus Ben Gardner
2005-12-10 10:45 ` Andrew Morton
2005-12-10 12:06 ` [lm-sensors] " Jean Delvare

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox