summaryrefslogtreecommitdiff
path: root/drivers/i2c/busses/i2c-ns9xxx.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/i2c/busses/i2c-ns9xxx.c')
-rw-r--r--drivers/i2c/busses/i2c-ns9xxx.c558
1 files changed, 558 insertions, 0 deletions
diff --git a/drivers/i2c/busses/i2c-ns9xxx.c b/drivers/i2c/busses/i2c-ns9xxx.c
new file mode 100644
index 000000000000..0b6d1eb3239b
--- /dev/null
+++ b/drivers/i2c/busses/i2c-ns9xxx.c
@@ -0,0 +1,558 @@
+/*
+ * linux/drivers/i2c/busses/i2c-ns9xxx.c
+ *
+ * based on old i2c-ns9xxx.c by Digi International Inc.
+ *
+ * Copyright (C) 2008 by Digi International Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/i2c-ns9xxx.h>
+#include <linux/platform_device.h>
+
+#include <asm/gpio.h>
+#include <asm/io.h>
+
+/* registers */
+#define I2C_CMD 0x00
+#define I2C_STATUS 0x00
+#define I2C_MASTERADDR 0x04
+#define I2C_SLAVEADDR 0x08
+#define I2C_CONFIG 0x0c
+
+/* command bit fields */
+#define I2C_CMD_TXVAL (1 << 13)
+#define I2C_MASTERADDR_7BIT 0
+#define I2C_MASTERADDR_10BIT 1
+
+/* configuration masks */
+#define I2C_CONFIG_CLREFMASK 0x000001ff
+#define I2C_MASTERADDR_ADDRMASK 0x000007ff
+
+/* configuration shifts */
+#define I2C_MASTERADDR_ADDRSHIFT 1
+
+/* shifted i2c commands */
+#define I2C_CMD_NOP 0
+#define I2C_CMD_READ (4 << 8)
+#define I2C_CMD_WRITE (5 << 8)
+#define I2C_CMD_STOP (6 << 8)
+
+/* interrupt causes */
+#define I2C_IRQ_MASK (0xf << 8)
+#define I2C_IRQ_ARBITLOST (1 << 8)
+#define I2C_IRQ_NOACK (2 << 8)
+#define I2C_IRQ_TXDATA (3 << 8)
+#define I2C_IRQ_RXDATA (4 << 8)
+#define I2C_IRQ_CMDACK (5 << 8)
+
+#define I2C_NORMALSPEED 100000
+#define I2C_HIGHSPEED 400000
+
+#define DRIVER_NAME "i2c-ns9xxx"
+
+enum i2c_int_state {
+ I2C_INT_AWAITING,
+ I2C_INT_OK,
+ I2C_INT_RETRY,
+ I2C_INT_ERROR,
+ I2C_INT_ABORT
+};
+
+struct ns9xxx_i2c {
+ struct i2c_adapter adap;
+ struct resource *mem;
+ struct clk *clk;
+
+ void __iomem *ioaddr;
+
+ spinlock_t lock;
+ wait_queue_head_t wait_q;
+
+ struct plat_ns9xxx_i2c *pdata;
+
+ char *buf;
+ int irq;
+ enum i2c_int_state state;
+};
+
+static int ns9xxx_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg msgs[], int num);
+
+static u32 ns9xxx_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR
+ | I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE
+ | I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA;
+}
+
+static struct i2c_algorithm ns9xxx_i2c_algo = {
+ .master_xfer = ns9xxx_i2c_xfer,
+ .functionality = ns9xxx_i2c_func,
+};
+
+static irqreturn_t ns9xxx_i2c_irq(int irqnr, void *dev_id)
+{
+ struct ns9xxx_i2c *dev_data = (struct ns9xxx_i2c *)dev_id;
+ unsigned int status;
+
+ /* acknowledge by reading */
+ status = readl(dev_data->ioaddr + I2C_CMD);
+
+ if (dev_data->state != I2C_INT_AWAITING)
+ return IRQ_HANDLED;
+
+ switch (status & I2C_IRQ_MASK) {
+ case I2C_IRQ_RXDATA:
+ spin_lock(&dev_data->lock);
+ if (dev_data->buf)
+ *dev_data->buf = status & 0xff;
+ spin_unlock(&dev_data->lock);
+ case I2C_IRQ_CMDACK:
+ case I2C_IRQ_TXDATA:
+ dev_data->state = I2C_INT_OK;
+ break;
+ case I2C_IRQ_NOACK:
+ writel(I2C_CMD_STOP, dev_data->ioaddr + I2C_CMD);
+ dev_data->state = I2C_INT_ABORT;
+ break;
+ case I2C_IRQ_ARBITLOST:
+ dev_data->state = I2C_INT_RETRY;
+ break;
+ default:
+ dev_data->state = I2C_INT_ERROR;
+ }
+
+ wake_up_interruptible(&dev_data->wait_q);
+
+ return IRQ_HANDLED;
+}
+
+static int ns9xxx_i2c_send_cmd(struct ns9xxx_i2c *dev_data, unsigned int cmd)
+{
+ dev_data->state = I2C_INT_AWAITING;
+ do {
+ writel(cmd, dev_data->ioaddr + I2C_CMD);
+ if (!wait_event_interruptible_timeout(dev_data->wait_q,
+ dev_data->state != I2C_INT_AWAITING,
+ dev_data->adap.timeout))
+ return -ETIMEDOUT;
+ } while (dev_data->state == I2C_INT_AWAITING);
+
+ if (dev_data->state != I2C_INT_OK)
+ return -EIO;
+
+ return 0;
+}
+
+static int ns9xxx_i2c_read(struct ns9xxx_i2c *dev_data, int count)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ while (count-- > 1) {
+ spin_lock_irqsave(&dev_data->lock, flags);
+ dev_data->buf++;
+ spin_unlock_irqrestore(&dev_data->lock, flags);
+
+ ret = ns9xxx_i2c_send_cmd(dev_data, I2C_CMD_NOP);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+static int ns9xxx_i2c_write(struct ns9xxx_i2c *dev_data,
+ const char *buf, int count)
+{
+ int ret = 0;
+
+ while (count--) {
+ ret = ns9xxx_i2c_send_cmd(dev_data,
+ I2C_CMD_NOP | I2C_CMD_TXVAL | *buf);
+ if (ret)
+ break;
+ buf++;
+ }
+
+ return ret;
+}
+
+static int ns9xxx_i2c_bitbang(struct ns9xxx_i2c *dev_data, struct i2c_msg *msg)
+{
+ int i, nr_bits, ret;
+
+ gpio_direction_output(dev_data->pdata->gpio_sda, 1);
+ gpio_direction_output(dev_data->pdata->gpio_scl, 1);
+ mdelay(10);
+
+ /* start */
+ gpio_set_value(dev_data->pdata->gpio_sda, 0);
+ mdelay(1);
+ gpio_set_value(dev_data->pdata->gpio_scl, 0);
+ mdelay(1);
+
+ nr_bits = (msg->flags & I2C_M_TEN) ? 10 : 7;
+ for (i = 0; i < nr_bits; i++) {
+ /* set data */
+ if (msg->addr & (1 << (nr_bits - i - 1)))
+ gpio_set_value(dev_data->pdata->gpio_sda, 1);
+ else
+ gpio_set_value(dev_data->pdata->gpio_sda, 1);
+ mdelay(1);
+
+ /* toggle clock */
+ gpio_set_value(dev_data->pdata->gpio_scl, 1);
+ mdelay(1);
+ gpio_set_value(dev_data->pdata->gpio_scl, 0);
+ mdelay(1);
+ }
+
+ /* read ack */
+ gpio_direction_input(dev_data->pdata->gpio_sda);
+ gpio_set_value(dev_data->pdata->gpio_scl, 1);
+ mdelay(1);
+ ret = gpio_get_value(dev_data->pdata->gpio_sda);
+
+ /* stop */
+ gpio_direction_output(dev_data->pdata->gpio_sda, 1);
+
+ return ret ? 0 : -ENODEV;
+}
+
+static int ns9xxx_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg msgs[], int num)
+{
+ struct ns9xxx_i2c *dev_data = (struct ns9xxx_i2c *)adap->algo_data;
+ int len, i, ret = 0, retry = 10;
+ unsigned long flags = 0;
+ unsigned int cmd, reg;
+ char *buf = NULL;
+
+ dev_data->state = I2C_INT_OK;
+
+ for (i = 0; i < num; i++) {
+ if (dev_data->state == I2C_INT_RETRY) {
+ ret = ns9xxx_i2c_send_cmd(dev_data, I2C_CMD_STOP);
+ --retry;
+ if (ret || !retry)
+ return -EIO;
+ }
+
+ len = msgs[i].len;
+ buf = msgs[i].buf;
+
+ spin_lock_irqsave(&dev_data->lock, flags);
+ dev_data->buf = buf;
+ spin_unlock_irqrestore(&dev_data->lock, flags);
+
+ if (msgs[i].len == 0) {
+ /* send using use bitbang mode */
+ ret = ns9xxx_i2c_bitbang(dev_data, &msgs[i]);
+ /* reset gpios to hardware i2c */
+ dev_data->pdata->gpio_configuration_func();
+ } else {
+ if (!(msgs[i].flags & I2C_M_NOSTART)) {
+ /* set device address */
+ reg = ((msgs[i].addr & I2C_MASTERADDR_ADDRMASK)
+ << I2C_MASTERADDR_ADDRSHIFT);
+
+ if (msgs[i].flags & I2C_M_TEN)
+ reg |= I2C_MASTERADDR_10BIT;
+ else
+ reg |= I2C_MASTERADDR_7BIT;
+
+ writel(reg, dev_data->ioaddr +
+ I2C_MASTERADDR);
+
+ if (msgs[i].flags & I2C_M_RD)
+ cmd = I2C_CMD_READ;
+ else {
+ cmd = I2C_CMD_WRITE | I2C_CMD_TXVAL;
+ cmd |= *buf;
+ len--;
+ buf++;
+ }
+
+ ret = ns9xxx_i2c_send_cmd(dev_data, cmd);
+ if (ret) {
+ if (dev_data->state == I2C_INT_RETRY) {
+ i = 0;
+ continue;
+ }
+ break;
+ }
+ }
+
+ if (msgs[i].flags & I2C_M_RD)
+ ret = ns9xxx_i2c_read(dev_data, len);
+ else
+ ret = ns9xxx_i2c_write(dev_data, buf, len);
+ if (ret) {
+ if (dev_data->state == I2C_INT_RETRY) {
+ i = 0;
+ continue;
+ }
+ break;
+ }
+ }
+ }
+
+ if (ns9xxx_i2c_send_cmd(dev_data, I2C_CMD_STOP)) {
+ /* sometimes interface gets stucked
+ * try to fix this by send "start, nop, start" */
+ ns9xxx_i2c_send_cmd(dev_data, I2C_CMD_NOP);
+ ns9xxx_i2c_send_cmd(dev_data, I2C_CMD_STOP);
+ }
+
+ spin_lock_irqsave(&dev_data->lock, flags);
+ dev_data->buf = NULL;
+ spin_unlock_irqrestore(&dev_data->lock, flags);
+
+ /* return ERROR or number of transmits */
+ return ((ret < 0) ? ret : i);
+}
+
+static int ns9xxx_i2c_set_clock(struct ns9xxx_i2c *dev_data, unsigned int freq)
+{
+ u32 config;
+
+ config = readl(dev_data->ioaddr + I2C_CONFIG) & ~I2C_CONFIG_CLREFMASK;
+
+ switch (freq) {
+ case I2C_NORMALSPEED:
+ config |= (((clk_get_rate(dev_data->clk) / (4 * freq))
+ - 4 - 3) / 2) & I2C_CONFIG_CLREFMASK;
+ break;
+ case I2C_HIGHSPEED:
+ config |= (((clk_get_rate(dev_data->clk) / (4 * freq))
+ - 4 - 24) * 2 / 3) & I2C_CONFIG_CLREFMASK;
+ break;
+ default:
+ pr_warning(DRIVER_NAME ": wrong clock configuration,"
+ " i2c won't work!\n");
+ return -EINVAL;
+ }
+
+ writel(config, dev_data->ioaddr + I2C_CONFIG);
+
+ return 0;
+}
+
+static int __devinit ns9xxx_i2c_probe(struct platform_device *pdev)
+{
+ struct ns9xxx_i2c *dev_data;
+ int ret;
+
+ dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
+ if (!dev_data) {
+ dev_dbg(&pdev->dev, "%s: err_alloc_dd\n", __func__);
+ ret = -ENOMEM;
+ goto err_alloc_dd;
+ }
+ platform_set_drvdata(pdev, dev_data);
+
+ dev_data->pdata = pdev->dev.platform_data;
+ if (!dev_data->pdata) {
+ dev_dbg(&pdev->dev, "%s: err_pdata\n", __func__);
+ ret = -ENOENT;
+ goto err_pdata;
+ }
+
+ snprintf(dev_data->adap.name, ARRAY_SIZE(dev_data->adap.name),
+ DRIVER_NAME);
+ dev_data->adap.owner = THIS_MODULE;
+ dev_data->adap.algo = &ns9xxx_i2c_algo;
+ dev_data->adap.algo_data = dev_data;
+ dev_data->adap.retries = 1;
+ dev_data->adap.timeout = HZ / 10;
+ dev_data->adap.class = I2C_CLASS_HWMON;
+ dev_data->buf = NULL;
+
+ spin_lock_init(&dev_data->lock);
+ init_waitqueue_head(&dev_data->wait_q);
+
+ dev_data->irq = platform_get_irq(pdev, 0);
+ if (dev_data->irq <= 0) {
+ dev_dbg(&pdev->dev, "%s: err_irq\n", __func__);
+ ret = -ENOENT;
+ goto err_irq;
+ }
+
+ dev_data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!dev_data->mem) {
+ dev_dbg(&pdev->dev, "%s: err_mem\n", __func__);
+ ret = -ENOENT;
+ goto err_mem;
+ }
+
+ if (!request_mem_region(dev_data->mem->start,
+ dev_data->mem->end - dev_data->mem->start + 1,
+ DRIVER_NAME)) {
+ dev_dbg(&pdev->dev, "%s: err_req_mem\n", __func__);
+ ret = -EBUSY;
+ goto err_req_mem;
+ }
+
+ dev_data->ioaddr = ioremap(dev_data->mem->start,
+ dev_data->mem->end - dev_data->mem->start + 1);
+ if (dev_data->ioaddr <= 0) {
+ dev_dbg(&pdev->dev, "%s: err_map_mem\n", __func__);
+ ret = -EBUSY;
+ goto err_map_mem;
+ } else
+ dev_dbg(&pdev->dev, "mapped I2C interface to virtual address"
+ "0x%x\n", (int)dev_data->ioaddr);
+
+ if (gpio_request(dev_data->pdata->gpio_scl, DRIVER_NAME)) {
+ dev_dbg(&pdev->dev, "%s: err_gpio_scl\n", __func__);
+ ret = -EBUSY;
+ goto err_gpio_scl;
+ }
+
+ if (gpio_request(dev_data->pdata->gpio_sda, DRIVER_NAME)) {
+ dev_dbg(&pdev->dev, "%s: err_gpio_sda\n", __func__);
+ ret = -EBUSY;
+ goto err_gpio_sda;
+ }
+
+ dev_data->clk = clk_get(&pdev->dev, DRIVER_NAME);
+ if (IS_ERR(dev_data->clk)) {
+ dev_dbg(&pdev->dev, "%s: err_clk_get\n", __func__);
+ ret = PTR_ERR(dev_data->clk);
+ goto err_clk_get;
+ }
+
+ ret = clk_enable(dev_data->clk);
+ if (ret) {
+ dev_dbg(&pdev->dev, "%s: err_clk_enable\n", __func__);
+ goto err_clk_enable;
+ }
+
+ /* configure i2c interface */
+ if (!dev_data->pdata->gpio_configuration_func) {
+ dev_dbg(&pdev->dev, "%s: err_cfg_gpio\n", __func__);
+ ret = -ENOENT;
+ goto err_cfg_gpio;
+ }
+ dev_data->pdata->gpio_configuration_func();
+
+ /* Set spike filter width to maximum value to workaround communication
+ * problems on the cc9p9360 module */
+ writel(readl(dev_data->ioaddr + I2C_CONFIG) | (0xf << 9),
+ dev_data->ioaddr + I2C_CONFIG);
+
+ if (dev_data->pdata->speed)
+ ret = ns9xxx_i2c_set_clock(dev_data, dev_data->pdata->speed);
+ else
+ ret = ns9xxx_i2c_set_clock(dev_data, I2C_NORMALSPEED);
+ if (ret) {
+ dev_dbg(&pdev->dev, "%s: err_set_clk\n", __func__);
+ goto err_set_clk;
+ }
+
+ ret = request_irq(dev_data->irq, ns9xxx_i2c_irq, 0,
+ DRIVER_NAME, dev_data);
+ if (ret) {
+ dev_dbg(&pdev->dev, "%s: err_req_irq\n", __func__);
+ ret = -EBUSY;
+ goto err_req_irq;
+ }
+
+ ret = i2c_add_numbered_adapter(&dev_data->adap);
+ if (ret < 0) {
+ dev_dbg(&pdev->dev, "%s: err_add_adap\n", __func__);
+ goto err_add_adap;
+ }
+
+ dev_info(&pdev->dev, "NS9XXX I2C adapter\n");
+
+ return 0;
+
+err_add_adap:
+ free_irq(dev_data->irq, dev_data);
+err_req_irq:
+err_set_clk:
+err_cfg_gpio:
+ clk_disable(dev_data->clk);
+err_clk_enable:
+ clk_put(dev_data->clk);
+err_clk_get:
+ gpio_free(dev_data->pdata->gpio_sda);
+err_gpio_sda:
+ gpio_free(dev_data->pdata->gpio_scl);
+err_gpio_scl:
+ iounmap(dev_data->ioaddr);
+err_map_mem:
+ release_mem_region(dev_data->mem->start,
+ dev_data->mem->end - dev_data->mem->start + 1);
+err_req_mem:
+err_mem:
+err_irq:
+err_pdata:
+err_alloc_dd:
+ kfree(dev_data);
+
+ return ret;
+}
+
+static int __devexit ns9xxx_i2c_remove(struct platform_device *pdev)
+{
+ struct ns9xxx_i2c *dev_data = platform_get_drvdata(pdev);
+
+ i2c_del_adapter(&dev_data->adap);
+
+ free_irq(dev_data->irq, dev_data);
+
+ clk_disable(dev_data->clk);
+ clk_put(dev_data->clk);
+
+ gpio_free(dev_data->pdata->gpio_sda);
+ gpio_free(dev_data->pdata->gpio_scl);
+
+ iounmap(dev_data->ioaddr);
+ release_mem_region(dev_data->mem->start,
+ dev_data->mem->end - dev_data->mem->start + 1);
+
+ kfree(dev_data);
+
+ return 0;
+}
+
+static struct platform_driver ns9xxx_i2c_driver = {
+ .probe = ns9xxx_i2c_probe,
+ .remove = __devexit_p(ns9xxx_i2c_remove),
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ }
+};
+
+static int __init ns9xxx_i2c_init(void)
+{
+ return platform_driver_register(&ns9xxx_i2c_driver);
+}
+
+static void __exit ns9xxx_i2c_exit(void)
+{
+ platform_driver_unregister(&ns9xxx_i2c_driver);
+}
+
+module_init(ns9xxx_i2c_init);
+module_exit(ns9xxx_i2c_exit);
+
+MODULE_AUTHOR("Matthias Ludwig");
+MODULE_DESCRIPTION("Digi NS9xxx I2C Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);