diff options
author | Gary King <gking@nvidia.com> | 2010-07-13 18:56:40 -0700 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2011-11-30 21:34:34 -0800 |
commit | de786eb5be611f8ba2fc8c784fa5f98f89b5a428 (patch) | |
tree | bd0698c16a6523cb27221f4344979e1468bea7f3 /drivers/i2c | |
parent | 9defc28533e93bd9b6298bdcba46c1c33e56d8d5 (diff) |
i2c-tegra: add support for virtual busses with dynamic pinmuxing
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>
Diffstat (limited to 'drivers/i2c')
-rw-r--r-- | drivers/i2c/busses/i2c-tegra.c | 134 |
1 files changed, 99 insertions, 35 deletions
diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index 3c94c4a81a55..10fdc96a338c 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -31,6 +31,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 @@ -120,10 +121,10 @@ */ 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; @@ -134,8 +135,20 @@ struct tegra_i2c_dev { u8 *msg_buf; size_t msg_buf_remaining; int msg_read; - 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_rate; + struct tegra_i2c_bus busses[1]; +}; + +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; }; static void dvc_writel(struct tegra_i2c_dev *i2c_dev, u32 val, unsigned long reg) @@ -353,7 +366,7 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) (0x2 << I2C_CNFG_DEBOUNCE_CNT_SHIFT); i2c_writel(i2c_dev, val, I2C_CNFG); i2c_writel(i2c_dev, 0, I2C_INT_MASK); - clk_set_rate(i2c_dev->clk, i2c_dev->bus_clk_rate * 8); + clk_set_rate(i2c_dev->clk, i2c_dev->last_bus_clk_rate * 8); if (!i2c_dev->is_dvc) { u32 sl_cfg = i2c_readl(i2c_dev, I2C_SL_CNFG); @@ -448,9 +461,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; @@ -529,21 +543,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 ret ?: i; } @@ -560,7 +594,7 @@ 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; @@ -568,8 +602,23 @@ static int tegra_i2c_probe(struct platform_device *pdev) const unsigned int *prop; 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"); @@ -609,7 +658,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; @@ -619,24 +669,24 @@ 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 = 100000; /* default clock rate */ + i2c_dev->last_bus_clk_rate = 100000; /* default clock rate */ if (pdata) { - i2c_dev->bus_clk_rate = pdata->bus_clk_rate; + i2c_dev->last_bus_clk_rate = pdata->bus_clk_rate[0]; } else if (i2c_dev->dev->of_node) { /* if there is a device tree node ... */ + /* TODO: DAN: this doesn't work for DT */ prop = of_get_property(i2c_dev->dev->of_node, "clock-frequency", NULL); if (prop) - i2c_dev->bus_clk_rate = be32_to_cpup(prop); + i2c_dev->last_bus_clk_rate = be32_to_cpup(prop); } + 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); @@ -655,26 +705,38 @@ 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; - i2c_dev->adapter.dev.of_node = pdev->dev.of_node; - - 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.dev.of_node = pdev->dev.of_node; + 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++; } of_i2c_register_devices(&i2c_dev->adapter); 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); @@ -692,7 +754,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); @@ -708,9 +772,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; } @@ -720,18 +784,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; } |