i2c-tegra: add support for virtual busses with dynamic pinmuxing
authorGary King <gking@nvidia.com>
Wed, 14 Jul 2010 01:56:40 +0000 (18:56 -0700)
committerColin Cross <ccross@android.com>
Wed, 6 Oct 2010 23:26:46 +0000 (16:26 -0700)
this adds support for dynamically reprogramming the I2C controller's
pin mux on transaction boundaries to enable one controller to be
registered as multiple I2C bus adapters with the kernel. this allows
platform designers an additional tool to resolve clock rate, I/O
voltage and electrical loading restrictions between the platform's
peripherals.

the i2c-tegra platform data is extended to support this; platforms
which use this feature should pass in the number of busses which
should be created for each controller, the starting adapter number
to use and the clock rate and pin mux for each virtual bus.

Change-Id: I57a96deb7b7b793222ec3f8cc3a941917a023609
Signed-off-by: Gary King <gking@nvidia.com>
drivers/i2c/busses/i2c-tegra.c
include/linux/i2c-tegra.h

index 69e8cdd46173a966ad0ed0bc22a6b06a37e6709e..b21a14d3ca22b3f89248fadafe1fb8fd9fcc4446 100644 (file)
@@ -29,6 +29,7 @@
 #include <asm/unaligned.h>
 
 #include <mach/clk.h>
+#include <mach/pinmux.h>
 
 #define TEGRA_I2C_TIMEOUT (msecs_to_jiffies(1000))
 #define BYTES_PER_FIFO_WORD 4
 #define I2C_HEADER_MASTER_ADDR_SHIFT           12
 #define I2C_HEADER_SLAVE_ADDR_SHIFT            1
 
+struct tegra_i2c_dev;
+
+struct tegra_i2c_bus {
+       struct tegra_i2c_dev *dev;
+       const struct tegra_pingroup_config *mux;
+       int mux_len;
+       unsigned long bus_clk_rate;
+       struct i2c_adapter adapter;
+};
+
 struct tegra_i2c_dev {
        struct device *dev;
-       struct i2c_adapter adapter;
        struct clk *clk;
        struct clk *i2c_clk;
        struct resource *iomem;
+       struct rt_mutex dev_lock;
        void __iomem *base;
        int cont_id;
        int irq;
@@ -108,8 +119,12 @@ struct tegra_i2c_dev {
        size_t msg_buf_remaining;
        int msg_read;
        int msg_transfer_complete;
-       unsigned long bus_clk_rate;
        bool is_suspended;
+       int bus_count;
+       const struct tegra_pingroup_config *last_mux;
+       int last_mux_len;
+       unsigned long last_bus_clk;
+       struct tegra_i2c_bus busses[1];
 };
 
 static void dvc_writel(struct tegra_i2c_dev *i2c_dev, u32 val, unsigned long reg)
@@ -305,7 +320,7 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev)
        val = I2C_CNFG_NEW_MASTER_FSM | I2C_CNFG_PACKET_MODE_EN;
        i2c_writel(i2c_dev, val, I2C_CNFG);
        i2c_writel(i2c_dev, 0, I2C_INT_MASK);
-       tegra_i2c_set_clk(i2c_dev, i2c_dev->bus_clk_rate);
+       tegra_i2c_set_clk(i2c_dev, i2c_dev->last_bus_clk);
 
        val = 7 << I2C_FIFO_CONTROL_TX_TRIG_SHIFT |
                0 << I2C_FIFO_CONTROL_RX_TRIG_SHIFT;
@@ -380,9 +395,10 @@ err:
        return IRQ_HANDLED;
 }
 
-static int tegra_i2c_xfer_msg(struct tegra_i2c_dev *i2c_dev,
+static int tegra_i2c_xfer_msg(struct tegra_i2c_bus *i2c_bus,
        struct i2c_msg *msg, int stop)
 {
+       struct tegra_i2c_dev *i2c_dev = i2c_bus->dev;
        u32 packet_header;
        u32 int_mask;
        int ret;
@@ -464,21 +480,41 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_dev *i2c_dev,
 
 static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
 {
-       struct tegra_i2c_dev *i2c_dev = i2c_get_adapdata(adap);
+       struct tegra_i2c_bus *i2c_bus = i2c_get_adapdata(adap);
+       struct tegra_i2c_dev *i2c_dev = i2c_bus->dev;
        int i;
        int ret = 0;
 
        if(i2c_dev->is_suspended)
                return -EBUSY;
 
+       rt_mutex_lock(&i2c_dev->dev_lock);
+
+       if (i2c_dev->last_mux != i2c_bus->mux) {
+               tegra_pinmux_set_safe_pinmux_table(i2c_dev->last_mux,
+                       i2c_dev->last_mux_len);
+               tegra_pinmux_config_pinmux_table(i2c_bus->mux,
+                       i2c_bus->mux_len);
+               i2c_dev->last_mux = i2c_bus->mux;
+               i2c_dev->last_mux_len = i2c_bus->mux_len;
+       }
+
+       if (i2c_dev->last_bus_clk != i2c_bus->bus_clk_rate) {
+               tegra_i2c_set_clk(i2c_dev, i2c_bus->bus_clk_rate);
+               i2c_dev->last_bus_clk = i2c_bus->bus_clk_rate;
+       }
+
        clk_enable(i2c_dev->clk);
        for (i = 0; i < num; i++) {
                int stop = (i == (num - 1)) ? 1  : 0;
-               ret = tegra_i2c_xfer_msg(i2c_dev, &msgs[i], stop);
+               ret = tegra_i2c_xfer_msg(i2c_bus, &msgs[i], stop);
                if (ret)
                        break;
        }
        clk_disable(i2c_dev->clk);
+
+       rt_mutex_unlock(&i2c_dev->dev_lock);
+
        return i;
 }
 
@@ -497,15 +533,30 @@ static const struct i2c_algorithm tegra_i2c_algo = {
 static int tegra_i2c_probe(struct platform_device *pdev)
 {
        struct tegra_i2c_dev *i2c_dev;
-       struct tegra_i2c_platform_data *pdata = pdev->dev.platform_data;
+       struct tegra_i2c_platform_data *plat = pdev->dev.platform_data;
        struct resource *res;
        struct resource *iomem;
        struct clk *clk;
        struct clk *i2c_clk;
        void *base;
        int irq;
+       int nbus;
+       int i = 0;
        int ret = 0;
 
+       if (!plat) {
+               dev_err(&pdev->dev, "no platform data?\n");
+               return -ENODEV;
+       }
+
+       if (plat->bus_count <= 0 || plat->adapter_nr < 0) {
+               dev_err(&pdev->dev, "invalid platform data?\n");
+               return -ENODEV;
+       }
+
+       WARN_ON(plat->bus_count > TEGRA_I2C_MAX_BUS);
+       nbus = min(TEGRA_I2C_MAX_BUS, plat->bus_count);
+
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!res) {
                dev_err(&pdev->dev, "no mem resource?\n");
@@ -543,7 +594,8 @@ static int tegra_i2c_probe(struct platform_device *pdev)
                goto err_clk_put;
        }
 
-       i2c_dev = kzalloc(sizeof(struct tegra_i2c_dev), GFP_KERNEL);
+       i2c_dev = kzalloc(sizeof(struct tegra_i2c_dev) +
+                         (nbus-1) * sizeof(struct tegra_i2c_bus), GFP_KERNEL);
        if (!i2c_dev) {
                ret = -ENOMEM;
                goto err_i2c_clk_put;
@@ -553,14 +605,13 @@ static int tegra_i2c_probe(struct platform_device *pdev)
        i2c_dev->clk = clk;
        i2c_dev->i2c_clk = i2c_clk;
        i2c_dev->iomem = iomem;
-       i2c_dev->adapter.algo = &tegra_i2c_algo;
        i2c_dev->irq = irq;
        i2c_dev->cont_id = pdev->id;
        i2c_dev->dev = &pdev->dev;
-       i2c_dev->bus_clk_rate = pdata ? pdata->bus_clk_rate : 100000;
+       i2c_dev->last_bus_clk = plat->bus_clk_rate[0] ?: 100000;
+       rt_mutex_init(&i2c_dev->dev_lock);
 
-       if (pdev->id == 3)
-               i2c_dev->is_dvc = 1;
+       i2c_dev->is_dvc = plat->is_dvc;
        init_completion(&i2c_dev->msg_complete);
 
        platform_set_drvdata(pdev, i2c_dev);
@@ -578,23 +629,35 @@ static int tegra_i2c_probe(struct platform_device *pdev)
 
        clk_enable(i2c_dev->i2c_clk);
 
-       i2c_set_adapdata(&i2c_dev->adapter, i2c_dev);
-       i2c_dev->adapter.owner = THIS_MODULE;
-       i2c_dev->adapter.class = I2C_CLASS_HWMON;
-       strlcpy(i2c_dev->adapter.name, "Tegra I2C adapter",
-               sizeof(i2c_dev->adapter.name));
-       i2c_dev->adapter.algo = &tegra_i2c_algo;
-       i2c_dev->adapter.dev.parent = &pdev->dev;
-       i2c_dev->adapter.nr = pdev->id;
-
-       ret = i2c_add_numbered_adapter(&i2c_dev->adapter);
-       if (ret) {
-               dev_err(&pdev->dev, "Failed to add I2C adapter\n");
-               goto err_free_irq;
+       for (i = 0; i < nbus; i++) {
+               struct tegra_i2c_bus *i2c_bus = &i2c_dev->busses[i];
+
+               i2c_bus->dev = i2c_dev;
+               i2c_bus->mux = plat->bus_mux[i];
+               i2c_bus->mux_len = plat->bus_mux_len[i];
+               i2c_bus->bus_clk_rate = plat->bus_clk_rate[i] ?: 100000;
+
+               i2c_bus->adapter.algo = &tegra_i2c_algo;
+               i2c_set_adapdata(&i2c_bus->adapter, i2c_bus);
+               i2c_bus->adapter.owner = THIS_MODULE;
+               i2c_bus->adapter.class = I2C_CLASS_HWMON;
+               strlcpy(i2c_bus->adapter.name, "Tegra I2C adapter",
+                       sizeof(i2c_bus->adapter.name));
+               i2c_bus->adapter.dev.parent = &pdev->dev;
+               i2c_bus->adapter.nr = plat->adapter_nr + i;
+               ret = i2c_add_numbered_adapter(&i2c_bus->adapter);
+               if (ret) {
+                       dev_err(&pdev->dev, "Failed to add I2C adapter\n");
+                       goto err_del_bus;
+               }
+               i2c_dev->bus_count++;
        }
 
        return 0;
-err_free_irq:
+
+err_del_bus:
+       while (i2c_dev->bus_count--)
+               i2c_del_adapter(&i2c_dev->busses[i2c_dev->bus_count].adapter);
        free_irq(i2c_dev->irq, i2c_dev);
 err_free:
        kfree(i2c_dev);
@@ -612,7 +675,9 @@ err_iounmap:
 static int tegra_i2c_remove(struct platform_device *pdev)
 {
        struct tegra_i2c_dev *i2c_dev = platform_get_drvdata(pdev);
-       i2c_del_adapter(&i2c_dev->adapter);
+       while (i2c_dev->bus_count--)
+               i2c_del_adapter(&i2c_dev->busses[i2c_dev->bus_count].adapter);
+
        free_irq(i2c_dev->irq, i2c_dev);
        clk_put(i2c_dev->i2c_clk);
        clk_put(i2c_dev->clk);
@@ -627,9 +692,9 @@ static int tegra_i2c_suspend(struct platform_device *pdev, pm_message_t state)
 {
        struct tegra_i2c_dev *i2c_dev = platform_get_drvdata(pdev);
 
-       i2c_lock_adapter(&i2c_dev->adapter);
+       rt_mutex_lock(&i2c_dev->dev_lock);
        i2c_dev->is_suspended = true;
-       i2c_unlock_adapter(&i2c_dev->adapter);
+       rt_mutex_unlock(&i2c_dev->dev_lock);
 
        return 0;
 }
@@ -639,18 +704,18 @@ static int tegra_i2c_resume(struct platform_device *pdev)
        struct tegra_i2c_dev *i2c_dev = platform_get_drvdata(pdev);
        int ret;
 
-       i2c_lock_adapter(&i2c_dev->adapter);
+       rt_mutex_lock(&i2c_dev->dev_lock);
 
        ret = tegra_i2c_init(i2c_dev);
 
        if (ret) {
-               i2c_unlock_adapter(&i2c_dev->adapter);
+               rt_mutex_unlock(&i2c_dev->dev_lock);
                return ret;
        }
 
        i2c_dev->is_suspended = false;
 
-       i2c_unlock_adapter(&i2c_dev->adapter);
+       rt_mutex_unlock(&i2c_dev->dev_lock);
 
        return 0;
 }
index 9c85da49857a240411e490dae1168d32e4a35288..6123502b8ddd66179f00fddbfee7b4c2267e33b3 100644 (file)
 #ifndef _LINUX_I2C_TEGRA_H
 #define _LINUX_I2C_TEGRA_H
 
+#include <mach/pinmux.h>
+
+#define TEGRA_I2C_MAX_BUS 3
+
 struct tegra_i2c_platform_data {
-       unsigned long bus_clk_rate;
+       int adapter_nr;
+       int bus_count;
+       const struct tegra_pingroup_config *bus_mux[TEGRA_I2C_MAX_BUS];
+       int bus_mux_len[TEGRA_I2C_MAX_BUS];
+       unsigned long bus_clk_rate[TEGRA_I2C_MAX_BUS];
+       bool is_dvc;
 };
 
 #endif /* _LINUX_I2C_TEGRA_H */