linux-mips
[Top] [All Lists]

[PATCH] I2C: SiByte: Convert the driver to make use of interrupts

To: Jean Delvare <khali@linux-fr.org>
Subject: [PATCH] I2C: SiByte: Convert the driver to make use of interrupts
From: Matt Turner <mattst88@gmail.com>
Date: Thu, 18 Aug 2011 19:43:11 -0400
Cc: linux-i2c@vger.kernel.org, linux-mips@linux-mips.org, Ralf Baechle <ralf@linux-mips.org>, Guenter Roeck <guenter.roeck@ericsson.com>, "Maciej W. Rozycki" <macro@linux-mips.org>, Matt Turner <mattst88@gmail.com>
Dkim-signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=from:to:cc:subject:date:message-id:x-mailer; bh=Y2oAVUy0gpbWudJRL16dFvmyLA/UtHsGT2CF4dXf+D0=; b=xGATxosQsLJ8qyOus7N6Fb1KH6soqyfW+twIXSazCByfGSF7trEfFTWvyoNcM0NpV2 Lz9wiAy3ZZgkWz33txRUENQ/TH4PWmrjONuSS+JOZPbIv/XFDsrewOh1bxvrGgCVU4Nv fhpTr36+/8BuFAhf6ghaUJnOp/Ni7YXxMm4AE=
Sender: linux-mips-bounce@linux-mips.org
From: Maciej W. Rozycki <macro@linux-mips.org>

This is a rewrite of large parts of the driver mainly so that it uses
SMBus interrupts to offload the CPU from busy-waiting on status inputs.
As a part of the overhaul of the init and exit calls, all accesses to the
hardware got converted to use accessory functions via an ioremap() cookie.

[mattst88] Added BCM1480 interrupts and rebased minimally.

Signed-off-by: Matt Turner <mattst88@gmail.com>
Signed-off-by: Maciej W. Rozycki <macro@linux-mips.org>
---
This is the second version of this patch that I've sent. This version
fixes the problem with the ENXIO return.

 drivers/i2c/busses/i2c-sibyte.c |  296 +++++++++++++++++++++++++++++---------
 1 files changed, 226 insertions(+), 70 deletions(-)

diff --git a/drivers/i2c/busses/i2c-sibyte.c b/drivers/i2c/busses/i2c-sibyte.c
index 0fe505d..d2f1cf1 100644
--- a/drivers/i2c/busses/i2c-sibyte.c
+++ b/drivers/i2c/busses/i2c-sibyte.c
@@ -2,6 +2,7 @@
  * Copyright (C) 2004 Steven J. Hill
  * Copyright (C) 2001,2002,2003 Broadcom Corporation
  * Copyright (C) 1995-2000 Simon G. Vogl
+ * Copyright (C) 2008 Maciej W. Rozycki
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -18,104 +19,164 @@
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
 
+#include <linux/errno.h>
+#include <linux/interrupt.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/i2c.h>
+#include <linux/param.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/wait.h>
 #include <linux/io.h>
+#include <asm/sibyte/sb1250_int.h>
 #include <asm/sibyte/sb1250_regs.h>
 #include <asm/sibyte/sb1250_smbus.h>
+#include <asm/sibyte/bcm1480_int.h>
 
 
 struct i2c_algo_sibyte_data {
-       void *data;             /* private data */
-       int   bus;              /* which bus */
-       void *reg_base;         /* CSR base */
+       wait_queue_head_t       wait;           /* IRQ queue */
+       void __iomem            *csr;           /* mapped CSR handle */
+       phys_t                  base;           /* physical CSR base */
+       char                    *name;          /* IRQ handler name */
+       spinlock_t              lock;           /* atomiser */
+       int                     irq;            /* IRQ line */
+       int                     status;         /* IRQ status */
 };
 
-/* ----- global defines ----------------------------------------------- */
-#define SMB_CSR(a,r) ((long)(a->reg_base + r))
 
+static irqreturn_t i2c_sibyte_interrupt(int irq, void *dev_id)
+{
+       struct i2c_adapter *i2c_adap = dev_id;
+       struct i2c_algo_sibyte_data *adap = i2c_adap->algo_data;
+       void __iomem *csr = adap->csr;
+       u8 status;
+
+       /*
+        * Ugh, no way to detect the finish interrupt,
+        * but if busy it is obviously not one.
+        */
+       status = __raw_readq(csr + R_SMB_STATUS);
+       if ((status & (M_SMB_ERROR | M_SMB_BUSY)) == M_SMB_BUSY)
+               return IRQ_NONE;
+
+       /*
+        * Clear the error interrupt (write 1 to clear);
+        * the finish interrupt was cleared by the read above.
+        */
+       __raw_writeq(status, csr + R_SMB_STATUS);
+
+       /* Post the status. */
+       spin_lock(&adap->lock);
+       adap->status = status & (M_SMB_ERROR_TYPE | M_SMB_ERROR | M_SMB_BUSY);
+       wake_up(&adap->wait);
+       spin_unlock(&adap->lock);
+
+       return IRQ_HANDLED;
+}
 
-static int smbus_xfer(struct i2c_adapter *i2c_adap, u16 addr,
-                     unsigned short flags, char read_write,
-                     u8 command, int size, union i2c_smbus_data * data)
+static s32 i2c_sibyte_smbus_xfer(struct i2c_adapter *i2c_adap, u16 addr,
+                                unsigned short cflags,
+                                char read_write, u8 command, int size,
+                                union i2c_smbus_data *data)
 {
        struct i2c_algo_sibyte_data *adap = i2c_adap->algo_data;
+       void __iomem *csr = adap->csr;
+       unsigned long flags;
        int data_bytes = 0;
        int error;
 
-       while (csr_in32(SMB_CSR(adap, R_SMB_STATUS)) & M_SMB_BUSY)
-               ;
+       spin_lock_irqsave(&adap->lock, flags);
+
+       if (adap->status < 0) {
+               error = -EIO;
+               goto out_unlock;
+       }
 
        switch (size) {
        case I2C_SMBUS_QUICK:
-               csr_out32((V_SMB_ADDR(addr) |
-                          (read_write == I2C_SMBUS_READ ? M_SMB_QDATA : 0) |
-                          V_SMB_TT_QUICKCMD), SMB_CSR(adap, R_SMB_START));
+               __raw_writeq(V_SMB_ADDR(addr) |
+                            (read_write == I2C_SMBUS_READ ? M_SMB_QDATA : 0) |
+                            V_SMB_TT_QUICKCMD,
+                            csr + R_SMB_START);
                break;
        case I2C_SMBUS_BYTE:
                if (read_write == I2C_SMBUS_READ) {
-                       csr_out32((V_SMB_ADDR(addr) | V_SMB_TT_RD1BYTE),
-                                 SMB_CSR(adap, R_SMB_START));
+                       __raw_writeq(V_SMB_ADDR(addr) | V_SMB_TT_RD1BYTE,
+                                    csr + R_SMB_START);
                        data_bytes = 1;
                } else {
-                       csr_out32(V_SMB_CMD(command), SMB_CSR(adap, R_SMB_CMD));
-                       csr_out32((V_SMB_ADDR(addr) | V_SMB_TT_WR1BYTE),
-                                 SMB_CSR(adap, R_SMB_START));
+                       __raw_writeq(V_SMB_CMD(command), csr + R_SMB_CMD);
+                       __raw_writeq(V_SMB_ADDR(addr) | V_SMB_TT_WR1BYTE,
+                                    csr + R_SMB_START);
                }
                break;
        case I2C_SMBUS_BYTE_DATA:
-               csr_out32(V_SMB_CMD(command), SMB_CSR(adap, R_SMB_CMD));
+               __raw_writeq(V_SMB_CMD(command), csr + R_SMB_CMD);
                if (read_write == I2C_SMBUS_READ) {
-                       csr_out32((V_SMB_ADDR(addr) | V_SMB_TT_CMD_RD1BYTE),
-                                 SMB_CSR(adap, R_SMB_START));
+                       __raw_writeq(V_SMB_ADDR(addr) | V_SMB_TT_CMD_RD1BYTE,
+                                    csr + R_SMB_START);
                        data_bytes = 1;
                } else {
-                       csr_out32(V_SMB_LB(data->byte),
-                                 SMB_CSR(adap, R_SMB_DATA));
-                       csr_out32((V_SMB_ADDR(addr) | V_SMB_TT_WR2BYTE),
-                                 SMB_CSR(adap, R_SMB_START));
+                       __raw_writeq(V_SMB_LB(data->byte), csr + R_SMB_DATA);
+                       __raw_writeq(V_SMB_ADDR(addr) | V_SMB_TT_WR2BYTE,
+                                    csr + R_SMB_START);
                }
                break;
        case I2C_SMBUS_WORD_DATA:
-               csr_out32(V_SMB_CMD(command), SMB_CSR(adap, R_SMB_CMD));
+               __raw_writeq(V_SMB_CMD(command), csr + R_SMB_CMD);
                if (read_write == I2C_SMBUS_READ) {
-                       csr_out32((V_SMB_ADDR(addr) | V_SMB_TT_CMD_RD2BYTE),
-                                 SMB_CSR(adap, R_SMB_START));
+                       __raw_writeq(V_SMB_ADDR(addr) | V_SMB_TT_CMD_RD2BYTE,
+                                    csr + R_SMB_START);
                        data_bytes = 2;
                } else {
-                       csr_out32(V_SMB_LB(data->word & 0xff),
-                                 SMB_CSR(adap, R_SMB_DATA));
-                       csr_out32(V_SMB_MB(data->word >> 8),
-                                 SMB_CSR(adap, R_SMB_DATA));
-                       csr_out32((V_SMB_ADDR(addr) | V_SMB_TT_WR2BYTE),
-                                 SMB_CSR(adap, R_SMB_START));
+                       __raw_writeq(V_SMB_LB(data->word & 0xff),
+                                    csr + R_SMB_DATA);
+                       __raw_writeq(V_SMB_MB(data->word >> 8),
+                                    csr + R_SMB_DATA);
+                       __raw_writeq(V_SMB_ADDR(addr) | V_SMB_TT_WR2BYTE,
+                                    csr + R_SMB_START);
                }
                break;
        default:
-               return -EOPNOTSUPP;
+               error = -EOPNOTSUPP;
+               goto out_unlock;
        }
+       mmiowb();
+       __raw_readq(csr + R_SMB_START);
+       adap->status = -1;
+
+       spin_unlock_irqrestore(&adap->lock, flags);
 
-       while (csr_in32(SMB_CSR(adap, R_SMB_STATUS)) & M_SMB_BUSY)
-               ;
+       wait_event_timeout(adap->wait, (adap->status >= 0), HZ);
 
-       error = csr_in32(SMB_CSR(adap, R_SMB_STATUS));
-       if (error & M_SMB_ERROR) {
-               /* Clear error bit by writing a 1 */
-               csr_out32(M_SMB_ERROR, SMB_CSR(adap, R_SMB_STATUS));
-               return (error & M_SMB_ERROR_TYPE) ? -EIO : -ENXIO;
+       spin_lock_irqsave(&adap->lock, flags);
+
+       if (adap->status > 0 && ((adap->status & (M_SMB_ERROR | 
M_SMB_ERROR_TYPE)) == M_SMB_ERROR)) {
+               error = -ENXIO;
+               goto out_unlock;
+       }
+       if (adap->status < 0 || (adap->status & (M_SMB_ERROR | M_SMB_BUSY))) {
+               error = -EIO;
+               goto out_unlock;
        }
 
        if (data_bytes == 1)
-               data->byte = csr_in32(SMB_CSR(adap, R_SMB_DATA)) & 0xff;
+               data->byte = __raw_readq(csr + R_SMB_DATA) & 0xff;
        if (data_bytes == 2)
-               data->word = csr_in32(SMB_CSR(adap, R_SMB_DATA)) & 0xffff;
+               data->word = __raw_readq(csr + R_SMB_DATA) & 0xffff;
 
-       return 0;
+       error = 0;
+
+out_unlock:
+       spin_unlock_irqrestore(&adap->lock, flags);
+
+       return error;
 }
 
-static u32 bit_func(struct i2c_adapter *adap)
+static u32 i2c_sibyte_bit_func(struct i2c_adapter *adap)
 {
        return (I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
                I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA);
@@ -125,8 +186,8 @@ static u32 bit_func(struct i2c_adapter *adap)
 /* -----exported algorithm data: ------------------------------------- */
 
 static const struct i2c_algorithm i2c_sibyte_algo = {
-       .smbus_xfer     = smbus_xfer,
-       .functionality  = bit_func,
+       .smbus_xfer     = i2c_sibyte_smbus_xfer,
+       .functionality  = i2c_sibyte_bit_func,
 };
 
 /*
@@ -135,37 +196,121 @@ static const struct i2c_algorithm i2c_sibyte_algo = {
 static int __init i2c_sibyte_add_bus(struct i2c_adapter *i2c_adap, int speed)
 {
        struct i2c_algo_sibyte_data *adap = i2c_adap->algo_data;
+       void __iomem *csr;
+       int err;
 
-       /* Register new adapter to i2c module... */
-       i2c_adap->algo = &i2c_sibyte_algo;
+       adap->status = 0;
+       init_waitqueue_head(&adap->wait);
+       spin_lock_init(&adap->lock);
+
+       csr = ioremap(adap->base, R_SMB_PEC + SMB_REGISTER_SPACING);
+       if (!csr) {
+               err = -ENOMEM;
+               goto out;
+       }
+       adap->csr = csr;
 
        /* Set the requested frequency. */
-       csr_out32(speed, SMB_CSR(adap,R_SMB_FREQ));
-       csr_out32(0, SMB_CSR(adap,R_SMB_CONTROL));
+       __raw_writeq(speed, csr + R_SMB_FREQ);
+
+       /* Clear any pending error interrupt. */
+       __raw_writeq(__raw_readq(csr + R_SMB_STATUS), csr + R_SMB_STATUS);
+       /* Disable interrupts. */
+       __raw_writeq(0, csr + R_SMB_CONTROL);
+       mmiowb();
+       __raw_readq(csr + R_SMB_CONTROL);
+
+       err = request_irq(adap->irq, i2c_sibyte_interrupt, IRQF_SHARED,
+                         adap->name, i2c_adap);
+       if (err < 0)
+               goto out_unmap;
+
+       /* Enable finish and error interrupts. */
+       __raw_writeq(M_SMB_FINISH_INTR | M_SMB_ERR_INTR, csr + R_SMB_CONTROL);
+
+       /* Register new adapter to i2c module... */
+       err = i2c_add_numbered_adapter(i2c_adap);
+       if (err < 0)
+               goto out_unirq;
+
+       return 0;
 
-       return i2c_add_numbered_adapter(i2c_adap);
+out_unirq:
+       /* Disable interrupts. */
+       __raw_writeq(0, csr + R_SMB_CONTROL);
+       mmiowb();
+       __raw_readq(csr + R_SMB_CONTROL);
+
+       free_irq(adap->irq, i2c_adap);
+
+       /* Clear any pending error interrupt. */
+       __raw_writeq(__raw_readq(csr + R_SMB_STATUS), csr + R_SMB_STATUS);
+out_unmap:
+       iounmap(csr);
+out:
+       return err;
 }
 
+static void i2c_sibyte_remove_bus(struct i2c_adapter *i2c_adap)
+{
+       struct i2c_algo_sibyte_data *adap = i2c_adap->algo_data;
+       void __iomem *csr = adap->csr;
+
+       i2c_del_adapter(i2c_adap);
+
+       /* Disable interrupts. */
+       __raw_writeq(0, csr + R_SMB_CONTROL);
+       mmiowb();
+       __raw_readq(csr + R_SMB_CONTROL);
 
-static struct i2c_algo_sibyte_data sibyte_board_data[2] = {
-       { NULL, 0, (void *) (CKSEG1+A_SMB_BASE(0)) },
-       { NULL, 1, (void *) (CKSEG1+A_SMB_BASE(1)) }
+       free_irq(adap->irq, i2c_adap);
+
+       /* Clear any pending error interrupt. */
+       __raw_writeq(__raw_readq(csr + R_SMB_STATUS), csr + R_SMB_STATUS);
+
+       iounmap(csr);
+}
+
+static struct i2c_algo_sibyte_data i2c_sibyte_board_data[2] = {
+#ifdef CONFIG_SIBYTE_SB1250
+       {
+               .name   = "sb1250-smbus-0",
+               .base   = A_SMB_0,
+               .irq    = K_INT_SMB_0,
+       },
+       {
+               .name   = "sb1250-smbus-1",
+               .base   = A_SMB_1,
+               .irq    = K_INT_SMB_1,
+       }
+#else
+       {
+               .name   = "bcm1480-smbus-0",
+               .base   = A_SMB_0,
+               .irq    = K_BCM1480_INT_SMB_0,
+       },
+       {
+               .name   = "bcm1480-smbus-1",
+               .base   = A_SMB_1,
+               .irq    = K_BCM1480_INT_SMB_1,
+       }
+#endif
 };
 
-static struct i2c_adapter sibyte_board_adapter[2] = {
+static struct i2c_adapter i2c_sibyte_board_adapter[2] = {
        {
                .owner          = THIS_MODULE,
                .class          = I2C_CLASS_HWMON | I2C_CLASS_SPD,
-               .algo           = NULL,
-               .algo_data      = &sibyte_board_data[0],
+               .algo           = &i2c_sibyte_algo,
+               .algo_data      = &i2c_sibyte_board_data[0],
                .nr             = 0,
                .name           = "SiByte SMBus 0",
        },
        {
                .owner          = THIS_MODULE,
                .class          = I2C_CLASS_HWMON | I2C_CLASS_SPD,
-               .algo           = NULL,
-               .algo_data      = &sibyte_board_data[1],
+               .algo           = &i2c_sibyte_algo,
+               .algo_data      = &i2c_sibyte_board_data[1],
                .nr             = 1,
                .name           = "SiByte SMBus 1",
        },
@@ -173,21 +318,32 @@ static struct i2c_adapter sibyte_board_adapter[2] = {
 
 static int __init i2c_sibyte_init(void)
 {
+       int err;
+
        pr_info("i2c-sibyte: i2c SMBus adapter module for SiByte board\n");
-       if (i2c_sibyte_add_bus(&sibyte_board_adapter[0], K_SMB_FREQ_100KHZ) < 0)
-               return -ENODEV;
-       if (i2c_sibyte_add_bus(&sibyte_board_adapter[1],
-                              K_SMB_FREQ_400KHZ) < 0) {
-               i2c_del_adapter(&sibyte_board_adapter[0]);
-               return -ENODEV;
-       }
+
+       err = i2c_sibyte_add_bus(&i2c_sibyte_board_adapter[0],
+                                K_SMB_FREQ_100KHZ);
+       if (err < 0)
+               goto out;
+
+       err = i2c_sibyte_add_bus(&i2c_sibyte_board_adapter[1],
+                                K_SMB_FREQ_400KHZ);
+       if (err < 0)
+               goto out_remove;
+
        return 0;
+
+out_remove:
+       i2c_sibyte_remove_bus(&i2c_sibyte_board_adapter[0]);
+out:
+       return err;
 }
 
 static void __exit i2c_sibyte_exit(void)
 {
-       i2c_del_adapter(&sibyte_board_adapter[0]);
-       i2c_del_adapter(&sibyte_board_adapter[1]);
+       i2c_sibyte_remove_bus(&i2c_sibyte_board_adapter[1]);
+       i2c_sibyte_remove_bus(&i2c_sibyte_board_adapter[0]);
 }
 
 module_init(i2c_sibyte_init);
-- 
1.7.3.4


<Prev in Thread] Current Thread [Next in Thread>