[PATCH] i2c: New bus driver for the OpenCores I2C controller
Peter Korsgaard [Sun, 4 Jun 2006 18:01:08 +0000 (20:01 +0200)]
The following patch adds support for the OpenCores I2C controller IP
core (See http://www.opencores.org/projects.cgi/web/i2c/overview).

Signed-off-by: Peter Korsgaard <jacmet@sunsite.dk>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Jean Delvare <khali@linux-fr.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

Documentation/i2c/busses/i2c-ocores [new file with mode: 0644]
drivers/i2c/busses/Kconfig
drivers/i2c/busses/Makefile
drivers/i2c/busses/i2c-ocores.c [new file with mode: 0644]
include/linux/i2c-ocores.h [new file with mode: 0644]

diff --git a/Documentation/i2c/busses/i2c-ocores b/Documentation/i2c/busses/i2c-ocores
new file mode 100644 (file)
index 0000000..cfcebb1
--- /dev/null
@@ -0,0 +1,51 @@
+Kernel driver i2c-ocores
+
+Supported adapters:
+  * OpenCores.org I2C controller by Richard Herveille (see datasheet link)
+    Datasheet: http://www.opencores.org/projects.cgi/web/i2c/overview
+
+Author: Peter Korsgaard <jacmet@sunsite.dk>
+
+Description
+-----------
+
+i2c-ocores is an i2c bus driver for the OpenCores.org I2C controller
+IP core by Richard Herveille.
+
+Usage
+-----
+
+i2c-ocores uses the platform bus, so you need to provide a struct
+platform_device with the base address and interrupt number. The
+dev.platform_data of the device should also point to a struct
+ocores_i2c_platform_data (see linux/i2c-ocores.h) describing the
+distance between registers and the input clock speed.
+
+E.G. something like:
+
+static struct resource ocores_resources[] = {
+       [0] = {
+               .start  = MYI2C_BASEADDR,
+               .end    = MYI2C_BASEADDR + 8,
+               .flags  = IORESOURCE_MEM,
+       },
+       [1] = {
+               .start  = MYI2C_IRQ,
+               .end    = MYI2C_IRQ,
+               .flags  = IORESOURCE_IRQ,
+       },
+};
+
+static struct ocores_i2c_platform_data myi2c_data = {
+       .regstep        = 2,            /* two bytes between registers */
+       .clock_khz      = 50000,        /* input clock of 50MHz */
+};
+
+static struct platform_device myi2c = {
+       .name                   = "ocores-i2c",
+       .dev = {
+               .platform_data  = &myi2c_data,
+       },
+       .num_resources          = ARRAY_SIZE(ocores_resources),
+       .resource               = ocores_resources,
+};
index d25a8cb..f7af7e9 100644 (file)
@@ -276,6 +276,17 @@ config I2C_NFORCE2
          This driver can also be built as a module.  If so, the module
          will be called i2c-nforce2.
 
+config I2C_OCORES
+       tristate "OpenCores I2C Controller"
+       depends on I2C && EXPERIMENTAL
+       help
+         If you say yes to this option, support will be included for the
+         OpenCores I2C controller. For details see
+         http://www.opencores.org/projects.cgi/web/i2c/overview
+
+         This driver can also be built as a module.  If so, the module
+         will be called i2c-ocores.
+
 config I2C_PARPORT
        tristate "Parallel port adapter"
        depends on I2C && PARPORT
index b44831d..ac56df5 100644 (file)
@@ -23,6 +23,7 @@ obj-$(CONFIG_I2C_POWERMAC)    += i2c-powermac.o
 obj-$(CONFIG_I2C_MPC)          += i2c-mpc.o
 obj-$(CONFIG_I2C_MV64XXX)      += i2c-mv64xxx.o
 obj-$(CONFIG_I2C_NFORCE2)      += i2c-nforce2.o
+obj-$(CONFIG_I2C_OCORES)       += i2c-ocores.o
 obj-$(CONFIG_I2C_PARPORT)      += i2c-parport.o
 obj-$(CONFIG_I2C_PARPORT_LIGHT)        += i2c-parport-light.o
 obj-$(CONFIG_I2C_PCA_ISA)      += i2c-pca-isa.o
diff --git a/drivers/i2c/busses/i2c-ocores.c b/drivers/i2c/busses/i2c-ocores.c
new file mode 100644 (file)
index 0000000..d5c0610
--- /dev/null
@@ -0,0 +1,343 @@
+/*
+ * i2c-ocores.c: I2C bus driver for OpenCores I2C controller
+ * (http://www.opencores.org/projects.cgi/web/i2c/overview).
+ *
+ * Peter Korsgaard <jacmet@sunsite.dk>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2.  This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/wait.h>
+#include <linux/i2c-ocores.h>
+#include <asm/io.h>
+
+struct ocores_i2c {
+       void __iomem *base;
+       int regstep;
+       wait_queue_head_t wait;
+       struct i2c_adapter adap;
+       struct i2c_msg *msg;
+       int pos;
+       int nmsgs;
+       int state; /* see STATE_ */
+};
+
+/* registers */
+#define OCI2C_PRELOW           0
+#define OCI2C_PREHIGH          1
+#define OCI2C_CONTROL          2
+#define OCI2C_DATA             3
+#define OCI2C_CMD              4
+#define OCI2C_STATUS           4
+
+#define OCI2C_CTRL_IEN         0x40
+#define OCI2C_CTRL_EN          0x80
+
+#define OCI2C_CMD_START                0x91
+#define OCI2C_CMD_STOP         0x41
+#define OCI2C_CMD_READ         0x21
+#define OCI2C_CMD_WRITE                0x11
+#define OCI2C_CMD_READ_ACK     0x21
+#define OCI2C_CMD_READ_NACK    0x29
+#define OCI2C_CMD_IACK         0x01
+
+#define OCI2C_STAT_IF          0x01
+#define OCI2C_STAT_TIP         0x02
+#define OCI2C_STAT_ARBLOST     0x20
+#define OCI2C_STAT_BUSY                0x40
+#define OCI2C_STAT_NACK                0x80
+
+#define STATE_DONE             0
+#define STATE_START            1
+#define STATE_WRITE            2
+#define STATE_READ             3
+#define STATE_ERROR            4
+
+static inline void oc_setreg(struct ocores_i2c *i2c, int reg, u8 value)
+{
+       iowrite8(value, i2c->base + reg * i2c->regstep);
+}
+
+static inline u8 oc_getreg(struct ocores_i2c *i2c, int reg)
+{
+       return ioread8(i2c->base + reg * i2c->regstep);
+}
+
+static void ocores_process(struct ocores_i2c *i2c)
+{
+       struct i2c_msg *msg = i2c->msg;
+       u8 stat = oc_getreg(i2c, OCI2C_STATUS);
+
+       if ((i2c->state == STATE_DONE) || (i2c->state == STATE_ERROR)) {
+               /* stop has been sent */
+               oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_IACK);
+               wake_up(&i2c->wait);
+               return;
+       }
+
+       /* error? */
+       if (stat & OCI2C_STAT_ARBLOST) {
+               i2c->state = STATE_ERROR;
+               oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP);
+               return;
+       }
+
+       if ((i2c->state == STATE_START) || (i2c->state == STATE_WRITE)) {
+               i2c->state =
+                       (msg->flags & I2C_M_RD) ? STATE_READ : STATE_WRITE;
+
+               if (stat & OCI2C_STAT_NACK) {
+                       i2c->state = STATE_ERROR;
+                       oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP);
+                       return;
+               }
+       } else
+               msg->buf[i2c->pos++] = oc_getreg(i2c, OCI2C_DATA);
+
+       /* end of msg? */
+       if (i2c->pos == msg->len) {
+               i2c->nmsgs--;
+               i2c->msg++;
+               i2c->pos = 0;
+               msg = i2c->msg;
+
+               if (i2c->nmsgs) {       /* end? */
+                       /* send start? */
+                       if (!(msg->flags & I2C_M_NOSTART)) {
+                               u8 addr = (msg->addr << 1);
+
+                               if (msg->flags & I2C_M_RD)
+                                       addr |= 1;
+
+                               i2c->state = STATE_START;
+
+                               oc_setreg(i2c, OCI2C_DATA, addr);
+                               oc_setreg(i2c, OCI2C_CMD,  OCI2C_CMD_START);
+                               return;
+                       } else
+                               i2c->state = (msg->flags & I2C_M_RD)
+                                       ? STATE_READ : STATE_WRITE;
+               } else {
+                       i2c->state = STATE_DONE;
+                       oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP);
+                       return;
+               }
+       }
+
+       if (i2c->state == STATE_READ) {
+               oc_setreg(i2c, OCI2C_CMD, i2c->pos == (msg->len-1) ?
+                         OCI2C_CMD_READ_NACK : OCI2C_CMD_READ_ACK);
+       } else {
+               oc_setreg(i2c, OCI2C_DATA, msg->buf[i2c->pos++]);
+               oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_WRITE);
+       }
+}
+
+static irqreturn_t ocores_isr(int irq, void *dev_id, struct pt_regs *regs)
+{
+       struct ocores_i2c *i2c = dev_id;
+
+       ocores_process(i2c);
+
+       return IRQ_HANDLED;
+}
+
+static int ocores_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+       struct ocores_i2c *i2c = i2c_get_adapdata(adap);
+
+       i2c->msg = msgs;
+       i2c->pos = 0;
+       i2c->nmsgs = num;
+       i2c->state = STATE_START;
+
+       oc_setreg(i2c, OCI2C_DATA,
+                       (i2c->msg->addr << 1) |
+                       ((i2c->msg->flags & I2C_M_RD) ? 1:0));
+
+       oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_START);
+
+       if (wait_event_timeout(i2c->wait, (i2c->state == STATE_ERROR) ||
+                              (i2c->state == STATE_DONE), HZ))
+               return (i2c->state == STATE_DONE) ? num : -EIO;
+       else
+               return -ETIMEDOUT;
+}
+
+static void ocores_init(struct ocores_i2c *i2c,
+                       struct ocores_i2c_platform_data *pdata)
+{
+       int prescale;
+       u8 ctrl = oc_getreg(i2c, OCI2C_CONTROL);
+
+       /* make sure the device is disabled */
+       oc_setreg(i2c, OCI2C_CONTROL, ctrl & ~(OCI2C_CTRL_EN|OCI2C_CTRL_IEN));
+
+       prescale = (pdata->clock_khz / (5*100)) - 1;
+       oc_setreg(i2c, OCI2C_PRELOW, prescale & 0xff);
+       oc_setreg(i2c, OCI2C_PREHIGH, prescale >> 8);
+
+       /* Init the device */
+       oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_IACK);
+       oc_setreg(i2c, OCI2C_CONTROL, ctrl | OCI2C_CTRL_IEN | OCI2C_CTRL_EN);
+}
+
+
+static u32 ocores_func(struct i2c_adapter *adap)
+{
+       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static struct i2c_algorithm ocores_algorithm = {
+       .master_xfer    = ocores_xfer,
+       .functionality  = ocores_func,
+};
+
+static struct i2c_adapter ocores_adapter = {
+       .owner          = THIS_MODULE,
+       .name           = "i2c-ocores",
+       .class          = I2C_CLASS_HWMON,
+       .algo           = &ocores_algorithm,
+       .timeout        = 2,
+       .retries        = 1,
+};
+
+
+static int __devinit ocores_i2c_probe(struct platform_device *pdev)
+{
+       struct ocores_i2c *i2c;
+       struct ocores_i2c_platform_data *pdata;
+       struct resource *res, *res2;
+       int ret;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res)
+               return -ENODEV;
+
+       res2 = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+       if (!res2)
+               return -ENODEV;
+
+       pdata = (struct ocores_i2c_platform_data*) pdev->dev.platform_data;
+       if (!pdata)
+               return -ENODEV;
+
+       i2c = kzalloc(sizeof(*i2c), GFP_KERNEL);
+       if (!i2c)
+               return -ENOMEM;
+
+       if (!request_mem_region(res->start, res->end - res->start + 1,
+                               pdev->name)) {
+               dev_err(&pdev->dev, "Memory region busy\n");
+               ret = -EBUSY;
+               goto request_mem_failed;
+       }
+
+       i2c->base = ioremap(res->start, res->end - res->start + 1);
+       if (!i2c->base) {
+               dev_err(&pdev->dev, "Unable to map registers\n");
+               ret = -EIO;
+               goto map_failed;
+       }
+
+       i2c->regstep = pdata->regstep;
+       ocores_init(i2c, pdata);
+
+       init_waitqueue_head(&i2c->wait);
+       ret = request_irq(res2->start, ocores_isr, 0, pdev->name, i2c);
+       if (ret) {
+               dev_err(&pdev->dev, "Cannot claim IRQ\n");
+               goto request_irq_failed;
+       }
+
+       /* hook up driver to tree */
+       platform_set_drvdata(pdev, i2c);
+       i2c->adap = ocores_adapter;
+       i2c_set_adapdata(&i2c->adap, i2c);
+       i2c->adap.dev.parent = &pdev->dev;
+
+       /* add i2c adapter to i2c tree */
+       ret = i2c_add_adapter(&i2c->adap);
+       if (ret) {
+               dev_err(&pdev->dev, "Failed to add adapter\n");
+               goto add_adapter_failed;
+       }
+
+       return 0;
+
+add_adapter_failed:
+       free_irq(res2->start, i2c);
+request_irq_failed:
+       iounmap(i2c->base);
+map_failed:
+       release_mem_region(res->start, res->end - res->start + 1);
+request_mem_failed:
+       kfree(i2c);
+
+       return ret;
+}
+
+static int __devexit ocores_i2c_remove(struct platform_device* pdev)
+{
+       struct ocores_i2c *i2c = platform_get_drvdata(pdev);
+       struct resource *res;
+
+       /* disable i2c logic */
+       oc_setreg(i2c, OCI2C_CONTROL, oc_getreg(i2c, OCI2C_CONTROL)
+                 & ~(OCI2C_CTRL_EN|OCI2C_CTRL_IEN));
+
+       /* remove adapter & data */
+       i2c_del_adapter(&i2c->adap);
+       platform_set_drvdata(pdev, NULL);
+
+       res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+       if (res)
+               free_irq(res->start, i2c);
+
+       iounmap(i2c->base);
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (res)
+               release_mem_region(res->start, res->end - res->start + 1);
+
+       kfree(i2c);
+
+       return 0;
+}
+
+static struct platform_driver ocores_i2c_driver = {
+       .probe  = ocores_i2c_probe,
+       .remove = __devexit_p(ocores_i2c_remove),
+       .driver = {
+               .owner = THIS_MODULE,
+               .name = "ocores-i2c",
+       },
+};
+
+static int __init ocores_i2c_init(void)
+{
+       return platform_driver_register(&ocores_i2c_driver);
+}
+
+static void __exit ocores_i2c_exit(void)
+{
+       platform_driver_unregister(&ocores_i2c_driver);
+}
+
+module_init(ocores_i2c_init);
+module_exit(ocores_i2c_exit);
+
+MODULE_AUTHOR("Peter Korsgaard <jacmet@sunsite.dk>");
+MODULE_DESCRIPTION("OpenCores I2C bus driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/i2c-ocores.h b/include/linux/i2c-ocores.h
new file mode 100644 (file)
index 0000000..8ed591b
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * i2c-ocores.h - definitions for the i2c-ocores interface
+ *
+ * Peter Korsgaard <jacmet@sunsite.dk>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2.  This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#ifndef _LINUX_I2C_OCORES_H
+#define _LINUX_I2C_OCORES_H
+
+struct ocores_i2c_platform_data {
+       u32 regstep;   /* distance between registers */
+       u32 clock_khz; /* input clock in kHz */
+};
+
+#endif /* _LINUX_I2C_OCORES_H */