i2c: New Atmel AT91 bus driver
authorAndrew Victor <andrew@sanpeople.com>
Sun, 10 Dec 2006 20:21:30 +0000 (21:21 +0100)
committerJean Delvare <khali@arrakis.delvare>
Sun, 10 Dec 2006 20:21:30 +0000 (21:21 +0100)
Add support for the I2C (Two-wire interface) controller integrated in
the Atmel AT91RM9200 processor.  This driver should also be usable on
the Atmel AT91SAM9261 and AT91SAM9260 processors.

Signed-off-by: Andrew Victor <andrew@sanpeople.com>
Signed-off-by: Mark M. Hoffman <mhoffman@lightlink.com>
Signed-off-by: Jean Delvare <khali@linux-fr.org>
drivers/i2c/busses/Kconfig
drivers/i2c/busses/Makefile
drivers/i2c/busses/i2c-at91.c [new file with mode: 0644]

index 291da25e84d1157f3c382418cafeae10d0c30f7f..ab398b70058667e5de0bc3a5ecfcd87be07cad5e 100644 (file)
@@ -74,6 +74,13 @@ config I2C_AMD8111
          This driver can also be built as a module.  If so, the module
          will be called i2c-amd8111.
 
+config I2C_AT91
+       tristate "Atmel AT91 I2C Two-Wire interface (TWI)"
+       depends on I2C && ARCH_AT91 && EXPERIMENTAL
+       help
+         This supports the use of the I2C interface on Atmel AT91
+         processors.
+
 config I2C_AU1550
        tristate "Au1550/Au1200 SMBus interface"
        depends on I2C && (SOC_AU1550 || SOC_AU1200)
index 626ac71af959fcac83e5d68bdb6bc688a7b7277e..a82b6bfad0466adf2b657347cc7883e351832c0d 100644 (file)
@@ -8,6 +8,7 @@ obj-$(CONFIG_I2C_ALI15X3)       += i2c-ali15x3.o
 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_AT91)         += i2c-at91.o
 obj-$(CONFIG_I2C_AU1550)       += i2c-au1550.o
 obj-$(CONFIG_I2C_ELEKTOR)      += i2c-elektor.o
 obj-$(CONFIG_I2C_HYDRA)                += i2c-hydra.o
diff --git a/drivers/i2c/busses/i2c-at91.c b/drivers/i2c/busses/i2c-at91.c
new file mode 100644 (file)
index 0000000..67f91bd
--- /dev/null
@@ -0,0 +1,325 @@
+/*
+    i2c Support for Atmel's AT91 Two-Wire Interface (TWI)
+
+    Copyright (C) 2004 Rick Bronson
+    Converted to 2.6 by Andrew Victor <andrew@sanpeople.com>
+
+    Borrowed heavily from original work by:
+    Copyright (C) 2000 Philip Edelbrock <phil@stimpy.netroedge.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; either version 2 of the License, or
+    (at your option) any later version.
+*/
+
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+
+#include <asm/io.h>
+
+#include <asm/arch/at91_twi.h>
+#include <asm/arch/board.h>
+#include <asm/arch/cpu.h>
+
+#define TWI_CLOCK              100000          /* Hz. max 400 Kbits/sec */
+
+
+static struct clk *twi_clk;
+static void __iomem *twi_base;
+
+#define at91_twi_read(reg)             __raw_readl(twi_base + (reg))
+#define at91_twi_write(reg, val)       __raw_writel((val), twi_base + (reg))
+
+
+/*
+ * Initialize the TWI hardware registers.
+ */
+static void __devinit at91_twi_hwinit(void)
+{
+       unsigned long cdiv, ckdiv;
+
+       at91_twi_write(AT91_TWI_IDR, 0xffffffff);       /* Disable all interrupts */
+       at91_twi_write(AT91_TWI_CR, AT91_TWI_SWRST);    /* Reset peripheral */
+       at91_twi_write(AT91_TWI_CR, AT91_TWI_MSEN);     /* Set Master mode */
+
+       /* Calcuate clock dividers */
+       cdiv = (clk_get_rate(twi_clk) / (2 * TWI_CLOCK)) - 3;
+       cdiv = cdiv + 1;        /* round up */
+       ckdiv = 0;
+       while (cdiv > 255) {
+               ckdiv++;
+               cdiv = cdiv >> 1;
+       }
+
+       if (cpu_is_at91rm9200()) {                      /* AT91RM9200 Errata #22 */
+               if (ckdiv > 5) {
+                       printk(KERN_ERR "AT91 I2C: Invalid TWI_CLOCK value!\n");
+                       ckdiv = 5;
+               }
+       }
+
+       at91_twi_write(AT91_TWI_CWGR, (ckdiv << 16) | (cdiv << 8) | cdiv);
+}
+
+/*
+ * Poll the i2c status register until the specified bit is set.
+ * Returns 0 if timed out (100 msec).
+ */
+static short at91_poll_status(unsigned long bit)
+{
+       int loop_cntr = 10000;
+
+       do {
+               udelay(10);
+       } while (!(at91_twi_read(AT91_TWI_SR) & bit) && (--loop_cntr > 0));
+
+       return (loop_cntr > 0);
+}
+
+static int xfer_read(struct i2c_adapter *adap, unsigned char *buf, int length)
+{
+       /* Send Start */
+       at91_twi_write(AT91_TWI_CR, AT91_TWI_START);
+
+       /* Read data */
+       while (length--) {
+               if (!length)    /* need to send Stop before reading last byte */
+                       at91_twi_write(AT91_TWI_CR, AT91_TWI_STOP);
+               if (!at91_poll_status(AT91_TWI_RXRDY)) {
+                       dev_dbg(&adap->dev, "RXRDY timeout\n");
+                       return -ETIMEDOUT;
+               }
+               *buf++ = (at91_twi_read(AT91_TWI_RHR) & 0xff);
+       }
+
+       return 0;
+}
+
+static int xfer_write(struct i2c_adapter *adap, unsigned char *buf, int length)
+{
+       /* Load first byte into transmitter */
+       at91_twi_write(AT91_TWI_THR, *buf++);
+
+       /* Send Start */
+       at91_twi_write(AT91_TWI_CR, AT91_TWI_START);
+
+       do {
+               if (!at91_poll_status(AT91_TWI_TXRDY)) {
+                       dev_dbg(&adap->dev, "TXRDY timeout\n");
+                       return -ETIMEDOUT;
+               }
+
+               length--;       /* byte was transmitted */
+
+               if (length > 0)         /* more data to send? */
+                       at91_twi_write(AT91_TWI_THR, *buf++);
+       } while (length);
+
+       /* Send Stop */
+       at91_twi_write(AT91_TWI_CR, AT91_TWI_STOP);
+
+       return 0;
+}
+
+/*
+ * Generic i2c master transfer entrypoint.
+ *
+ * Note: We do not use Atmel's feature of storing the "internal device address".
+ * Instead the "internal device address" has to be written using a seperate
+ * i2c message.
+ * http://lists.arm.linux.org.uk/pipermail/linux-arm-kernel/2004-September/024411.html
+ */
+static int at91_xfer(struct i2c_adapter *adap, struct i2c_msg *pmsg, int num)
+{
+       int i, ret;
+
+       dev_dbg(&adap->dev, "at91_xfer: processing %d messages:\n", num);
+
+       for (i = 0; i < num; i++) {
+               dev_dbg(&adap->dev, " #%d: %sing %d byte%s %s 0x%02x\n", i,
+                       pmsg->flags & I2C_M_RD ? "read" : "writ",
+                       pmsg->len, pmsg->len > 1 ? "s" : "",
+                       pmsg->flags & I2C_M_RD ? "from" : "to", pmsg->addr);
+
+               at91_twi_write(AT91_TWI_MMR, (pmsg->addr << 16)
+                       | ((pmsg->flags & I2C_M_RD) ? AT91_TWI_MREAD : 0));
+
+               if (pmsg->len && pmsg->buf) {   /* sanity check */
+                       if (pmsg->flags & I2C_M_RD)
+                               ret = xfer_read(adap, pmsg->buf, pmsg->len);
+                       else
+                               ret = xfer_write(adap, pmsg->buf, pmsg->len);
+
+                       if (ret)
+                               return ret;
+
+                       /* Wait until transfer is finished */
+                       if (!at91_poll_status(AT91_TWI_TXCOMP)) {
+                               dev_dbg(&adap->dev, "TXCOMP timeout\n");
+                               return -ETIMEDOUT;
+                       }
+               }
+               dev_dbg(&adap->dev, "transfer complete\n");
+               pmsg++;         /* next message */
+       }
+       return i;
+}
+
+/*
+ * Return list of supported functionality.
+ */
+static u32 at91_func(struct i2c_adapter *adapter)
+{
+       return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static struct i2c_algorithm at91_algorithm = {
+       .master_xfer    = at91_xfer,
+       .functionality  = at91_func,
+};
+
+/*
+ * Main initialization routine.
+ */
+static int __devinit at91_i2c_probe(struct platform_device *pdev)
+{
+       struct i2c_adapter *adapter;
+       struct resource *res;
+       int rc;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res)
+               return -ENXIO;
+
+       if (!request_mem_region(res->start, res->end - res->start + 1, "at91_i2c"))
+               return -EBUSY;
+
+       twi_base = ioremap(res->start, res->end - res->start + 1);
+       if (!twi_base) {
+               rc = -ENOMEM;
+               goto fail0;
+       }
+
+       twi_clk = clk_get(NULL, "twi_clk");
+       if (IS_ERR(twi_clk)) {
+               dev_err(&pdev->dev, "no clock defined\n");
+               rc = -ENODEV;
+               goto fail1;
+       }
+
+       adapter = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL);
+       if (adapter == NULL) {
+               dev_err(&pdev->dev, "can't allocate inteface!\n");
+               rc = -ENOMEM;
+               goto fail2;
+       }
+       sprintf(adapter->name, "AT91");
+       adapter->algo = &at91_algorithm;
+       adapter->class = I2C_CLASS_HWMON;
+       adapter->dev.parent = &pdev->dev;
+
+       platform_set_drvdata(pdev, adapter);
+
+       clk_enable(twi_clk);            /* enable peripheral clock */
+       at91_twi_hwinit();              /* initialize TWI controller */
+
+       rc = i2c_add_adapter(adapter);
+       if (rc) {
+               dev_err(&pdev->dev, "Adapter %s registration failed\n",
+                               adapter->name);
+               goto fail3;
+       }
+
+       dev_info(&pdev->dev, "AT91 i2c bus driver.\n");
+       return 0;
+
+fail3:
+       platform_set_drvdata(pdev, NULL);
+       kfree(adapter);
+       clk_disable(twi_clk);
+fail2:
+       clk_put(twi_clk);
+fail1:
+       iounmap(twi_base);
+fail0:
+       release_mem_region(res->start, res->end - res->start + 1);
+
+       return rc;
+}
+
+static int __devexit at91_i2c_remove(struct platform_device *pdev)
+{
+       struct i2c_adapter *adapter = platform_get_drvdata(pdev);
+       struct resource *res;
+       int rc;
+
+       rc = i2c_del_adapter(adapter);
+       platform_set_drvdata(pdev, NULL);
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       iounmap(twi_base);
+       release_mem_region(res->start, res->end - res->start + 1);
+
+       clk_disable(twi_clk);           /* disable peripheral clock */
+       clk_put(twi_clk);
+
+       return rc;
+}
+
+#ifdef CONFIG_PM
+
+/* NOTE: could save a few mA by keeping clock off outside of at91_xfer... */
+
+static int at91_i2c_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+       clk_disable(twi_clk);
+       return 0;
+}
+
+static int at91_i2c_resume(struct platform_device *pdev)
+{
+       return clk_enable(twi_clk);
+}
+
+#else
+#define at91_i2c_suspend       NULL
+#define at91_i2c_resume                NULL
+#endif
+
+static struct platform_driver at91_i2c_driver = {
+       .probe          = at91_i2c_probe,
+       .remove         = __devexit_p(at91_i2c_remove),
+       .suspend        = at91_i2c_suspend,
+       .resume         = at91_i2c_resume,
+       .driver         = {
+               .name   = "at91_i2c",
+               .owner  = THIS_MODULE,
+       },
+};
+
+static int __init at91_i2c_init(void)
+{
+       return platform_driver_register(&at91_i2c_driver);
+}
+
+static void __exit at91_i2c_exit(void)
+{
+       platform_driver_unregister(&at91_i2c_driver);
+}
+
+module_init(at91_i2c_init);
+module_exit(at91_i2c_exit);
+
+MODULE_AUTHOR("Rick Bronson");
+MODULE_DESCRIPTION("I2C (TWI) driver for Atmel AT91");
+MODULE_LICENSE("GPL");