2016-04-18 13:16:19

by Shardar Mohammed

[permalink] [raw]
Subject: [PATCH v3 1/2] i2c: tegra: add separate function for config_load

- Define separate function for configuration load register handling
to make it use by different functions later.
- Instead of calculating timeout for the config load during init,
calculate it when config load register is written. Also use the
msecs_to_jiffies for timeout calculation instead of macro HZ.

Signed-off-by: Shardar Shariff Md <[email protected]>

Changes since v1:
- Add separate function for config load handling
---
drivers/i2c/busses/i2c-tegra.c | 39 +++++++++++++++++++++++++--------------
1 file changed, 25 insertions(+), 14 deletions(-)

diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c
index d764d64..6235f16 100644
--- a/drivers/i2c/busses/i2c-tegra.c
+++ b/drivers/i2c/busses/i2c-tegra.c
@@ -423,12 +423,31 @@ static inline void tegra_i2c_clock_disable(struct tegra_i2c_dev *i2c_dev)
clk_disable(i2c_dev->fast_clk);
}

+static int tegra_i2c_wait_for_config_load(struct tegra_i2c_dev *i2c_dev)
+{
+ unsigned long timeout;
+
+ if (i2c_dev->hw->has_config_load_reg) {
+ i2c_writel(i2c_dev, I2C_MSTR_CONFIG_LOAD, I2C_CONFIG_LOAD);
+ timeout = jiffies + msecs_to_jiffies(1000);
+ while (i2c_readl(i2c_dev, I2C_CONFIG_LOAD) != 0) {
+ if (time_after(jiffies, timeout)) {
+ dev_warn(i2c_dev->dev,
+ "timeout waiting for config load\n");
+ return -ETIMEDOUT;
+ }
+ msleep(1);
+ }
+ }
+
+ return 0;
+}
+
static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev)
{
u32 val;
int err = 0;
u32 clk_divisor;
- unsigned long timeout = jiffies + HZ;

err = tegra_i2c_clock_enable(i2c_dev);
if (err < 0) {
@@ -477,25 +496,17 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev)
if (i2c_dev->is_multimaster_mode && i2c_dev->hw->has_slcg_override_reg)
i2c_writel(i2c_dev, I2C_MST_CORE_CLKEN_OVR, I2C_CLKEN_OVERRIDE);

- if (i2c_dev->hw->has_config_load_reg) {
- i2c_writel(i2c_dev, I2C_MSTR_CONFIG_LOAD, I2C_CONFIG_LOAD);
- while (i2c_readl(i2c_dev, I2C_CONFIG_LOAD) != 0) {
- if (time_after(jiffies, timeout)) {
- dev_warn(i2c_dev->dev,
- "timeout waiting for config load\n");
- return -ETIMEDOUT;
- }
- msleep(1);
- }
- }
-
- tegra_i2c_clock_disable(i2c_dev);
+ err = tegra_i2c_wait_for_config_load(i2c_dev);
+ if (err)
+ goto err;

if (i2c_dev->irq_disabled) {
i2c_dev->irq_disabled = 0;
enable_irq(i2c_dev->irq);
}

+err:
+ tegra_i2c_clock_disable(i2c_dev);
return err;
}

--
1.8.1.5


2016-04-18 13:16:29

by Shardar Mohammed

[permalink] [raw]
Subject: [PATCH v3 2/2] i2c: tegra: proper handling of error cases

To summarize the issue observed in error cases:

SW Flow: For i2c message transfer, packet header and data payload is
posted and then required error/packet completion interrupts are enabled
later.

HW flow: HW process the packet just after packet header is posted, if
ARB lost/NACK error occurs (SW will not handle immediately when error
happens as error interrupts are not enabled at this point). HW assumes
error is acknowledged and clears current data in FIFO, But SW here posts
the remaining data payload which still stays in FIFO as stale data
(data without packet header).

Now once the interrupts are enabled, SW handles ARB lost/NACK error by
clearing the ARB lost/NACK interrupt. Now HW assumes that SW attended
the error and will parse/process stale data (data without packet header)
present in FIFO which causes invalid NACK errors.

Fix: Enable the error interrupts before posting the packet into FIFO
which make sure HW to not clear the fifo. Also disable the packet mode
before acknowledging errors (ARB lost/NACK error) to not process any
stale data. As error interrupts are enabled before posting the packet
header use spinlock to avoid preempting.

Signed-off-by: Shardar Shariff Md <[email protected]>

Changes since v1:
- Align the commit message to 72 characters per line.
- Removing unnecessary paranthesis.
- Printing error if tegra_i2c_disable_packet_mode() fails
is already present and handling error is not taken cared
in ISR which was done in v2.
---
drivers/i2c/busses/i2c-tegra.c | 28 ++++++++++++++++++++++++++--
1 file changed, 26 insertions(+), 2 deletions(-)

diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c
index 6235f16..7922892 100644
--- a/drivers/i2c/busses/i2c-tegra.c
+++ b/drivers/i2c/busses/i2c-tegra.c
@@ -191,6 +191,7 @@ struct tegra_i2c_dev {
u16 clk_divisor_non_hs_mode;
bool is_suspended;
bool is_multimaster_mode;
+ spinlock_t xfer_lock;
};

static void dvc_writel(struct tegra_i2c_dev *i2c_dev, u32 val, unsigned long reg)
@@ -510,14 +511,27 @@ err:
return err;
}

+static int tegra_i2c_disable_packet_mode(struct tegra_i2c_dev *i2c_dev)
+{
+ u32 cnfg;
+
+ cnfg = i2c_readl(i2c_dev, I2C_CNFG);
+ if (cnfg & I2C_CNFG_PACKET_MODE_EN)
+ i2c_writel(i2c_dev, cnfg & ~I2C_CNFG_PACKET_MODE_EN, I2C_CNFG);
+
+ return tegra_i2c_wait_for_config_load(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;
struct tegra_i2c_dev *i2c_dev = dev_id;
+ unsigned long flags;

status = i2c_readl(i2c_dev, I2C_INT_STATUS);

+ spin_lock_irqsave(&i2c_dev->xfer_lock, flags);
if (status == 0) {
dev_warn(i2c_dev->dev, "irq status 0 %08x %08x %08x\n",
i2c_readl(i2c_dev, I2C_PACKET_TRANSFER_STATUS),
@@ -533,6 +547,7 @@ static irqreturn_t tegra_i2c_isr(int irq, void *dev_id)
}

if (unlikely(status & status_err)) {
+ tegra_i2c_disable_packet_mode(i2c_dev);
if (status & I2C_INT_NO_ACK)
i2c_dev->msg_err |= I2C_ERR_NO_ACK;
if (status & I2C_INT_ARBITRATION_LOST)
@@ -562,7 +577,7 @@ static irqreturn_t tegra_i2c_isr(int irq, void *dev_id)
BUG_ON(i2c_dev->msg_buf_remaining);
complete(&i2c_dev->msg_complete);
}
- return IRQ_HANDLED;
+ goto done;
err:
/* An error occurred, mask all interrupts */
tegra_i2c_mask_irq(i2c_dev, I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST |
@@ -573,6 +588,8 @@ err:
dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS);

complete(&i2c_dev->msg_complete);
+done:
+ spin_unlock_irqrestore(&i2c_dev->xfer_lock, flags);
return IRQ_HANDLED;
}

@@ -582,6 +599,7 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_dev *i2c_dev,
u32 packet_header;
u32 int_mask;
unsigned long time_left;
+ unsigned long flags;

tegra_i2c_flush_fifos(i2c_dev);

@@ -594,6 +612,11 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_dev *i2c_dev,
i2c_dev->msg_read = (msg->flags & I2C_M_RD);
reinit_completion(&i2c_dev->msg_complete);

+ spin_lock_irqsave(&i2c_dev->xfer_lock, flags);
+
+ int_mask = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST;
+ tegra_i2c_unmask_irq(i2c_dev, int_mask);
+
packet_header = (0 << PACKET_HEADER0_HEADER_SIZE_SHIFT) |
PACKET_HEADER0_PROTOCOL_I2C |
(i2c_dev->cont_id << PACKET_HEADER0_CONT_ID_SHIFT) |
@@ -623,14 +646,15 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_dev *i2c_dev,
if (!(msg->flags & I2C_M_RD))
tegra_i2c_fill_tx_fifo(i2c_dev);

- int_mask = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST;
if (i2c_dev->hw->has_per_pkt_xfer_complete_irq)
int_mask |= I2C_INT_PACKET_XFER_COMPLETE;
if (msg->flags & I2C_M_RD)
int_mask |= I2C_INT_RX_FIFO_DATA_REQ;
else if (i2c_dev->msg_buf_remaining)
int_mask |= I2C_INT_TX_FIFO_DATA_REQ;
+
tegra_i2c_unmask_irq(i2c_dev, int_mask);
+ spin_unlock_irqrestore(&i2c_dev->xfer_lock, flags);
dev_dbg(i2c_dev->dev, "unmasked irq: %02x\n",
i2c_readl(i2c_dev, I2C_INT_MASK));

--
1.8.1.5

2016-04-18 14:10:51

by Thierry Reding

[permalink] [raw]
Subject: Re: [PATCH v3 1/2] i2c: tegra: add separate function for config_load

On Mon, Apr 18, 2016 at 06:45:54PM +0530, Shardar Shariff Md wrote:
> - Define separate function for configuration load register handling
> to make it use by different functions later.
> - Instead of calculating timeout for the config load during init,
> calculate it when config load register is written. Also use the
> msecs_to_jiffies for timeout calculation instead of macro HZ.

Looking really good now. One minor nit: If you need to resort to lists
in the commit message, it's usually a sign that you can split things up
into further patches. In this particular case I think moving the timeout
computation can be considered implicit in splitting apart the function.
That is, I'd simply skip the second list item (and remove the - from the
first line).

> Signed-off-by: Shardar Shariff Md <[email protected]>
>
> Changes since v1:
> - Add separate function for config load handling
> ---

Almost perfect. The changelog should go *below* the --- separator. This
is somewhat tricky to do because the separator will only be added by git
format-patch. You can either manually move it after git format-patch or
add the separator to the commit message. If you do the latter you'll get
two separators, but git send-email/apply/am will do the right things and
skip everything after the first separator and the beginning of the diff.

Finally, one nitpick below, which you don't necessarily have to address.

> drivers/i2c/busses/i2c-tegra.c | 39 +++++++++++++++++++++++++--------------
> 1 file changed, 25 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c
> index d764d64..6235f16 100644
> --- a/drivers/i2c/busses/i2c-tegra.c
> +++ b/drivers/i2c/busses/i2c-tegra.c
> @@ -423,12 +423,31 @@ static inline void tegra_i2c_clock_disable(struct tegra_i2c_dev *i2c_dev)
> clk_disable(i2c_dev->fast_clk);
> }
>
> +static int tegra_i2c_wait_for_config_load(struct tegra_i2c_dev *i2c_dev)
> +{
> + unsigned long timeout;
> +
> + if (i2c_dev->hw->has_config_load_reg) {

You could move the timeout variable declaration into the conditional
block and initialize it immediately. That saves one line of code and
restricts the scope of the variable to the conditional.

Thierry


Attachments:
(No filename) (2.19 kB)
signature.asc (819.00 B)
Download all attachments

2016-04-18 14:17:11

by Thierry Reding

[permalink] [raw]
Subject: Re: [PATCH v3 2/2] i2c: tegra: proper handling of error cases

On Mon, Apr 18, 2016 at 06:45:55PM +0530, Shardar Shariff Md wrote:
> To summarize the issue observed in error cases:
>
> SW Flow: For i2c message transfer, packet header and data payload is
> posted and then required error/packet completion interrupts are enabled
> later.
>
> HW flow: HW process the packet just after packet header is posted, if
> ARB lost/NACK error occurs (SW will not handle immediately when error
> happens as error interrupts are not enabled at this point). HW assumes
> error is acknowledged and clears current data in FIFO, But SW here posts
> the remaining data payload which still stays in FIFO as stale data
> (data without packet header).
>
> Now once the interrupts are enabled, SW handles ARB lost/NACK error by
> clearing the ARB lost/NACK interrupt. Now HW assumes that SW attended
> the error and will parse/process stale data (data without packet header)
> present in FIFO which causes invalid NACK errors.
>
> Fix: Enable the error interrupts before posting the packet into FIFO
> which make sure HW to not clear the fifo. Also disable the packet mode
> before acknowledging errors (ARB lost/NACK error) to not process any
> stale data. As error interrupts are enabled before posting the packet
> header use spinlock to avoid preempting.
>
> Signed-off-by: Shardar Shariff Md <[email protected]>
>
> Changes since v1:
> - Align the commit message to 72 characters per line.
> - Removing unnecessary paranthesis.
> - Printing error if tegra_i2c_disable_packet_mode() fails
> is already present and handling error is not taken cared
> in ISR which was done in v2.

I think I didn't express myself very clearly, or gave insufficient
information. The changelog reads easier if you have one entry per
revision. The above would then read:

Changes in v3:
- Printing error if tegra_i2c_disable_packet_mode() fails
is already present and handling error is not taken cared
in ISR which was done in v2.

Changes in v2:
- Align the commit message to 72 characters per line.
- Removing unnecessary paranthesis.

And it should go below the --- separator.

One more nitpick below.

> ---
> drivers/i2c/busses/i2c-tegra.c | 28 ++++++++++++++++++++++++++--
> 1 file changed, 26 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c
> index 6235f16..7922892 100644
> --- a/drivers/i2c/busses/i2c-tegra.c
> +++ b/drivers/i2c/busses/i2c-tegra.c
> @@ -191,6 +191,7 @@ struct tegra_i2c_dev {
> u16 clk_divisor_non_hs_mode;
> bool is_suspended;
> bool is_multimaster_mode;
> + spinlock_t xfer_lock;
> };
>
> static void dvc_writel(struct tegra_i2c_dev *i2c_dev, u32 val, unsigned long reg)
> @@ -510,14 +511,27 @@ err:
> return err;
> }
>
> +static int tegra_i2c_disable_packet_mode(struct tegra_i2c_dev *i2c_dev)
> +{
> + u32 cnfg;
> +
> + cnfg = i2c_readl(i2c_dev, I2C_CNFG);
> + if (cnfg & I2C_CNFG_PACKET_MODE_EN)
> + i2c_writel(i2c_dev, cnfg & ~I2C_CNFG_PACKET_MODE_EN, I2C_CNFG);
> +
> + return tegra_i2c_wait_for_config_load(i2c_dev);
> +}

Since you no longer use the return value you might as well just make the
function return void. I don't feel very strongly about it, though, so
I'll defer to Wolfram.

Thierry


Attachments:
(No filename) (3.16 kB)
signature.asc (819.00 B)
Download all attachments

2016-04-19 09:31:50

by Jon Hunter

[permalink] [raw]
Subject: Re: [PATCH v3 1/2] i2c: tegra: add separate function for config_load


On 18/04/16 14:15, Shardar Shariff Md wrote:
> - Define separate function for configuration load register handling
> to make it use by different functions later.
> - Instead of calculating timeout for the config load during init,
> calculate it when config load register is written. Also use the
> msecs_to_jiffies for timeout calculation instead of macro HZ.
>
> Signed-off-by: Shardar Shariff Md <[email protected]>
>
> Changes since v1:
> - Add separate function for config load handling
> ---
> drivers/i2c/busses/i2c-tegra.c | 39 +++++++++++++++++++++++++--------------
> 1 file changed, 25 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c
> index d764d64..6235f16 100644
> --- a/drivers/i2c/busses/i2c-tegra.c
> +++ b/drivers/i2c/busses/i2c-tegra.c
> @@ -423,12 +423,31 @@ static inline void tegra_i2c_clock_disable(struct tegra_i2c_dev *i2c_dev)
> clk_disable(i2c_dev->fast_clk);
> }
>
> +static int tegra_i2c_wait_for_config_load(struct tegra_i2c_dev *i2c_dev)
> +{
> + unsigned long timeout;
> +
> + if (i2c_dev->hw->has_config_load_reg) {
> + i2c_writel(i2c_dev, I2C_MSTR_CONFIG_LOAD, I2C_CONFIG_LOAD);
> + timeout = jiffies + msecs_to_jiffies(1000);
> + while (i2c_readl(i2c_dev, I2C_CONFIG_LOAD) != 0) {
> + if (time_after(jiffies, timeout)) {
> + dev_warn(i2c_dev->dev,
> + "timeout waiting for config load\n");
> + return -ETIMEDOUT;
> + }
> + msleep(1);
> + }

How about using the readx_poll_timeout() (include/linux/iopoll.h) here?

Cheers
Jon