i386: CS5535 chip support - SMBus
PATCH 3/3 i386: CS5535 chip support - SMBus
Provides a SMBus/I2C driver for the AMD CS5535, modeled after the scx200_acb driver.
Signed-off-by: Ben Gardner <bgardner at wabtec.com> -------------- next part --------------
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 at 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 at 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 at 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
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?
Hi Ben,
Quoted 2 lines
Index: linux-2.6.14/drivers/i2c/busses/Makefile
===================================================================
If you are using quilt, please set QUILT_NO_DIFF_INDEX=1.
drivers/i2c/busses/Kconfig config I2C_AU1550 · line 91
Quoted 11 lines
drivers/i2c/busses/Kconfig +5
-0@@ -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.
drivers/i2c/busses/i2c-cs5535.c line 1
Quoted 4 lines
drivers/i2c/busses/i2c-cs5535.c new file +1
-0@@ -0,0 +1,553 @@ +/* linux/drivers/i2c/i2c-cs5535.c
Redundant.
drivers/i2c/busses/i2c-cs5535.c line 5
Quoted 4 lines
drivers/i2c/busses/i2c-cs5535.c new file +4
-0+* +* Copyright (c) 2005 Ben Gardner <[email protected]> +* +* AMD CS5535 SMB support - mostly identical to
SMBus.
drivers/i2c/busses/i2c-cs5535.c line 10
Quoted 5 lines
drivers/i2c/busses/i2c-cs5535.c new file +5
-0+* National Semiconductor SCx200 ACCESS.bus support +* except for the detection routine. +* +* Based on scx200_acb.c which is: +* Copyright (c) 2001,2002 Christer Weinigel <[email protected]>
Why not extend that driver then, rather than having a separate one?
drivers/i2c/busses/i2c-cs5535.c line 11
Quoted 1 line
drivers/i2c/busses/i2c-cs5535.c new file +1
-0+#include <linux/config.h>
This file should no more be included explicitely.
drivers/i2c/busses/i2c-cs5535.c line 12
Quoted 1 line
drivers/i2c/busses/i2c-cs5535.c new file +1
-0+#include <linux/pci.h>
Why would you need this?
drivers/i2c/busses/i2c-cs5535.c line 13
Quoted 1 line
drivers/i2c/busses/i2c-cs5535.c new file +1
-0+#include <linux/interrupt.h>
And this?
drivers/i2c/busses/i2c-cs5535.c line 14
Quoted 1 line
drivers/i2c/busses/i2c-cs5535.c new file +1
-0+MODULE_DESCRIPTION ("AMD CS5535 SMB Driver" );
SMBus :)
drivers/i2c/busses/i2c-cs5535.c line 16
Quoted 2 lines
drivers/i2c/busses/i2c-cs5535.c new file +2
-0+/* Needed to see if the cs5535 is present */ +extern u32 cs5535_gpio_base;
Move this to a header file.
drivers/i2c/busses/i2c-cs5535.c line 23
Quoted 7 lines
drivers/i2c/busses/i2c-cs5535.c new file +7
-0+#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.
drivers/i2c/busses/i2c-cs5535.c line 32
Quoted 9 lines
drivers/i2c/busses/i2c-cs5535.c new file +9
-0+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.
drivers/i2c/busses/i2c-cs5535.c line 35
Quoted 3 lines
drivers/i2c/busses/i2c-cs5535.c new file +3
-0+static void cs5535_smb_poll (struct cs5535_smb_iface *iface) +{ +u8 status =0 ;
Useless initialization.
drivers/i2c/busses/i2c-cs5535.c line 40
Quoted 5 lines
drivers/i2c/busses/i2c-cs5535.c new file +5
-0+unsigned long timeout; + + timeout = jiffies +POLL_TIMEOUT ; +while (time_before (jiffies, timeout)) { +
No blank line at beginning of blocks please.
drivers/i2c/busses/i2c-cs5535.c line 44
Quoted 4 lines
drivers/i2c/busses/i2c-cs5535.c new file +4
-0+/* 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.
Quoted 17 lines
drivers/i2c/busses/i2c-cs5535.c new file +16
-0+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) +{
drivers/i2c/busses/i2c-cs5535.c
Quoted 10 lines
drivers/i2c/busses/i2c-cs5535.c new file +9
-0+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".
drivers/i2c/busses/i2c-cs5535.c
Quoted 2 lines
drivers/i2c/busses/i2c-cs5535.c new file +2
-0+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.
drivers/i2c/busses/i2c-cs5535.c
Quoted 9 lines
drivers/i2c/busses/i2c-cs5535.c new file +9
-0+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.
drivers/i2c/busses/i2c-cs5535.c
Quoted 3 lines
drivers/i2c/busses/i2c-cs5535.c new file +3
-0+ iface->address_byte = address <<1 ; +if (rw ==I2C_SMBUS_READ ) + iface->address_byte |=1 ;
Can be simplified to:
iface->address_byte = (address << 1) | rw;
drivers/i2c/busses/i2c-cs5535.c
Quoted 5 lines
drivers/i2c/busses/i2c-cs5535.c new file +5
-0+ 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?
drivers/i2c/busses/i2c-cs5535.c
Quoted 5 lines
drivers/i2c/busses/i2c-cs5535.c new file +5
-0+static int __init cs5535_smb_create (int base,int index) +{ +struct cs5535_smb_iface *iface; +struct i2c_adapter *adapter; +int rc =0 ;
Useless initialization.
drivers/i2c/busses/i2c-cs5535.c
Quoted 1 line
drivers/i2c/busses/i2c-cs5535.c new file +1
-0+if (request_region (iface->base,SMB_IO_SIZE , adapter->name) ==0 ) {
if (!request_region(...)) {
drivers/i2c/busses/i2c-cs5535.c
Quoted 5 lines
drivers/i2c/busses/i2c-cs5535.c new file +5
-0+ 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!
drivers/i2c/busses/i2c-cs5535.c
Quoted 8 lines
drivers/i2c/busses/i2c-cs5535.c new file +8
-0+ +cs5535_smb_reset (iface); + +if (i2c_add_adapter (adapter) <0 ) { +dev_err (&adapter->dev,"failed to register\n" ); + rc = -ENODEV ; + goto errout; + }
Ditto.
drivers/i2c/busses/i2c-cs5535.c
Quoted 8 lines
drivers/i2c/busses/i2c-cs5535.c new file +8
-0+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).
drivers/i2c/busses/i2c-cs5535.c
Quoted 5 lines
drivers/i2c/busses/i2c-cs5535.c new file +4
-0+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.
drivers/i2c/busses/i2c-cs5535.c
Quoted 8 lines
drivers/i2c/busses/i2c-cs5535.c new file +8
-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); + } +}
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.
drivers/i2c/busses/i2c-cs5535.c
Quoted 4 lines
drivers/i2c/busses/i2c-cs5535.c new file +4
-0+ +module_init (cs5535_smb_init); +module_exit (cs5535_smb_cleanup); +
No blank line at end of file please.
include/linux/i2c-id.h line 259
Quoted 7 lines
include/linux/i2c-id.h +1
-0@@ -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,
Signature · 1 line
Jean Delvare
PATCH 3/3 i386: CS5535 chip support - SMBus
In reply to Ben GardnerHi Ben,
Quoted 5 lines
Quoted 1 line
Why not extend that driver then, rather than having a separate one?
(...)
I started off thinking that the two wouldn't be compatible... and never re-examined what I was doing.
Oh, well. I hope I didn't waste too much of your time!
No worry. It's still better to realize it now than having duplicated code with twice as much maintenance work.
Thanks,
Signature · 1 line
Jean Delvare