diff options
author | Alok Chauhan <alokc@nvidia.com> | 2011-05-05 10:32:20 +0530 |
---|---|---|
committer | Varun Colbert <vcolbert@nvidia.com> | 2011-05-12 18:56:08 -0700 |
commit | 8b3c04964dc8692bc70b0ab32abad4a55e9d90c8 (patch) | |
tree | a4b20f74fe593df14212ae7fb2b20340b21e820c /drivers | |
parent | b86a702cc73c202260a6d52e71e28503e5f7946a (diff) |
i2c: tegra: Making proper synchronization between isr and caller
Following are the changes:
- Added a mechanism to handle synchronization between caller thread
and isr so that isr should not get called when clock is disable or
controller is in reset state.
- Handling tx fifo overflow error.
- Added delay before resetting the controller which is required if
there is any error in previous transaction so proper stop signal
will be generated.
Change-Id: I7ac121e056193cd4601f032cdabab59e7e0946ea
Reviewed-on: http://git-master/r/30457
Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
Reviewed-by: Alok Chauhan <alokc@nvidia.com>
Tested-by: Alok Chauhan <alokc@nvidia.com>
Tested-by: Laxman Dewangan <ldewangan@nvidia.com>
Reviewed-by: Danielle Sun <dsun@nvidia.com>
Tested-by: Danielle Sun <dsun@nvidia.com>
Reviewed-by: Bo Yan <byan@nvidia.com>
Reviewed-by: Scott Williams <scwilliams@nvidia.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/i2c/busses/i2c-tegra.c | 60 |
1 files changed, 51 insertions, 9 deletions
diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index 08d0a7f0a8b9..f9a14a75563c 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -31,6 +31,7 @@ #include <linux/delay.h> #include <linux/slab.h> #include <linux/i2c-tegra.h> +#include <linux/spinlock.h> #include <asm/unaligned.h> @@ -125,10 +126,12 @@ struct tegra_i2c_dev { struct clk *clk; struct resource *iomem; struct rt_mutex dev_lock; + spinlock_t clk_lock; void __iomem *base; int cont_id; int irq; bool irq_disabled; + bool controller_enabled; int is_dvc; bool is_slave; struct completion msg_complete; @@ -343,6 +346,10 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) if (!i2c_dev->is_clkon_always) clk_enable(i2c_dev->clk); + /* Interrupt generated before sending stop signal so + * wait for some time so that stop signal can be send proerly */ + udelay(100); + tegra_periph_reset_assert(i2c_dev->clk); udelay(2); tegra_periph_reset_deassert(i2c_dev->clk); @@ -379,8 +386,16 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) static irqreturn_t tegra_i2c_isr(int irq, void *dev_id) { u32 status; - const u32 status_err = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST; + const u32 status_err = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST | I2C_INT_TX_FIFO_OVERFLOW; struct tegra_i2c_dev *i2c_dev = dev_id; + unsigned long flags; + + spin_lock_irqsave(&i2c_dev->clk_lock, flags); + if (!i2c_dev->controller_enabled) { + dev_warn(i2c_dev->dev, "Controller not enabled\n"); + spin_unlock_irqrestore(&i2c_dev->clk_lock, flags); + return IRQ_NONE; + } status = i2c_readl(i2c_dev, I2C_INT_STATUS); @@ -394,7 +409,6 @@ static irqreturn_t tegra_i2c_isr(int irq, void *dev_id) i2c_dev->irq_disabled = 1; } - complete(&i2c_dev->msg_complete); goto err; } @@ -416,7 +430,13 @@ static irqreturn_t tegra_i2c_isr(int irq, void *dev_id) i2c_readl(i2c_dev, I2C_PACKET_TRANSFER_STATUS)); } - complete(&i2c_dev->msg_complete); + if (status & I2C_INT_TX_FIFO_OVERFLOW) { + i2c_dev->msg_err |= I2C_INT_TX_FIFO_OVERFLOW; + dev_warn(i2c_dev->dev, "Tx fifo overflow during " + " communicate to add 0x%x\n", i2c_dev->msg_add); + dev_warn(i2c_dev->dev, "Packet status 0x%08x\n", + i2c_readl(i2c_dev, I2C_PACKET_TRANSFER_STATUS)); + } goto err; } @@ -432,7 +452,6 @@ static irqreturn_t tegra_i2c_isr(int irq, void *dev_id) i2c_dev->irq_disabled = 1; } - complete(&i2c_dev->msg_complete); goto err; } @@ -453,14 +472,15 @@ static irqreturn_t tegra_i2c_isr(int irq, void *dev_id) if (status & I2C_INT_PACKET_XFER_COMPLETE) i2c_dev->msg_transfer_complete = 1; - if (i2c_dev->msg_transfer_complete && !i2c_dev->msg_buf_remaining) - complete(&i2c_dev->msg_complete); - i2c_writel(i2c_dev, status, I2C_INT_STATUS); if (i2c_dev->is_dvc) dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS); + if (i2c_dev->msg_transfer_complete && !i2c_dev->msg_buf_remaining) + complete(&i2c_dev->msg_complete); + + spin_unlock_irqrestore(&i2c_dev->clk_lock, flags); return IRQ_HANDLED; err: @@ -487,13 +507,15 @@ err: /* An error occured, mask all interrupts */ tegra_i2c_mask_irq(i2c_dev, I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST | I2C_INT_PACKET_XFER_COMPLETE | I2C_INT_TX_FIFO_DATA_REQ | - I2C_INT_RX_FIFO_DATA_REQ); + I2C_INT_RX_FIFO_DATA_REQ | I2C_INT_TX_FIFO_OVERFLOW); i2c_writel(i2c_dev, status, I2C_INT_STATUS); if (i2c_dev->is_dvc) dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS); + complete(&i2c_dev->msg_complete); + spin_unlock_irqrestore(&i2c_dev->clk_lock, flags); return IRQ_HANDLED; } @@ -503,6 +525,7 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_bus *i2c_bus, struct tegra_i2c_dev *i2c_dev = i2c_bus->dev; u32 int_mask; int ret; + unsigned long flags; tegra_i2c_flush_fifos(i2c_dev); @@ -542,7 +565,7 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_bus *i2c_bus, if (!(msg->flags & I2C_M_RD)) tegra_i2c_fill_tx_fifo(i2c_dev); - int_mask = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST; + int_mask = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST | I2C_INT_TX_FIFO_OVERFLOW; if (msg->flags & I2C_M_RD) int_mask |= I2C_INT_RX_FIFO_DATA_REQ; else if (i2c_dev->msg_buf_remaining) @@ -559,7 +582,10 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_bus *i2c_bus, "i2c transfer timed out, addr 0x%04x, data 0x%02x\n", msg->addr, msg->buf[0]); + spin_lock_irqsave(&i2c_dev->clk_lock, flags); + i2c_dev->controller_enabled = false; tegra_i2c_init(i2c_dev); + spin_unlock_irqrestore(&i2c_dev->clk_lock, flags); return -ETIMEDOUT; } @@ -569,7 +595,11 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_bus *i2c_bus, if (likely(i2c_dev->msg_err == I2C_ERR_NONE)) return 0; + spin_lock_irqsave(&i2c_dev->clk_lock, flags); + i2c_dev->controller_enabled = false; tegra_i2c_init(i2c_dev); + spin_unlock_irqrestore(&i2c_dev->clk_lock, flags); + if (i2c_dev->msg_err == I2C_ERR_NO_ACK) { if (msg->flags & I2C_M_IGNORE_NAK) return 0; @@ -589,6 +619,7 @@ static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], struct tegra_i2c_dev *i2c_dev = i2c_bus->dev; int i; int ret = 0; + unsigned long flags; if (i2c_dev->is_suspended) return -EBUSY; @@ -614,6 +645,11 @@ static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], if (!i2c_dev->is_clkon_always) clk_enable(i2c_dev->clk); + + spin_lock_irqsave(&i2c_dev->clk_lock, flags); + i2c_dev->controller_enabled = true; + spin_unlock_irqrestore(&i2c_dev->clk_lock, flags); + for (i = 0; i < num; i++) { int stop = (i == (num - 1)) ? 1 : 0; ret = tegra_i2c_xfer_msg(i2c_bus, &msgs[i], stop); @@ -623,6 +659,10 @@ static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], ret = i; out: + spin_lock_irqsave(&i2c_dev->clk_lock, flags); + i2c_dev->controller_enabled = false; + spin_unlock_irqrestore(&i2c_dev->clk_lock, flags); + if (!i2c_dev->is_clkon_always) clk_disable(i2c_dev->clk); @@ -720,7 +760,9 @@ static int tegra_i2c_probe(struct platform_device *pdev) i2c_dev->last_bus_clk = plat->bus_clk_rate[0] ?: 100000; i2c_dev->msgs = NULL; i2c_dev->msgs_num = 0; + i2c_dev->controller_enabled = false; rt_mutex_init(&i2c_dev->dev_lock); + spin_lock_init(&i2c_dev->clk_lock); i2c_dev->slave_addr = plat->slave_addr; i2c_dev->is_dvc = plat->is_dvc; |