Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5a0d5f5f authored by Troy Kisky's avatar Troy Kisky Committed by Jean Delvare
Browse files

i2c-davinci: Fix signal handling bug



If wait_for_completion_interruptible_timeout exits due
to a signal, the i2c bus was locking up.

Signed-off-by: default avatarTroy Kisky <troy.kisky@boundarydevices.com>
Signed-off-by: default avatarKevin Hilman <khilman@mvista.com>
Signed-off-by: default avatarJean Delvare <khali@linux-fr.org>
parent 0ab56e20
Loading
Loading
Loading
Loading
+52 −10
Original line number Diff line number Diff line
@@ -85,6 +85,7 @@
#define DAVINCI_I2C_MDR_MST	(1 << 10)
#define DAVINCI_I2C_MDR_TRX	(1 << 9)
#define DAVINCI_I2C_MDR_XA	(1 << 8)
#define DAVINCI_I2C_MDR_RM	(1 << 7)
#define DAVINCI_I2C_MDR_IRS	(1 << 5)

#define DAVINCI_I2C_IMR_AAS	(1 << 6)
@@ -112,6 +113,7 @@ struct davinci_i2c_dev {
	u8			*buf;
	size_t			buf_len;
	int			irq;
	u8			terminate;
	struct i2c_adapter	adapter;
};

@@ -283,20 +285,34 @@ i2c_davinci_xfer_msg(struct i2c_adapter *adap, struct i2c_msg *msg, int stop)
		MOD_REG_BIT(w, DAVINCI_I2C_IMR_XRDY, 1);
	davinci_i2c_write_reg(dev, DAVINCI_I2C_IMR_REG, w);

	dev->terminate = 0;
	/* write the data into mode register */
	davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, flag);

	r = wait_for_completion_interruptible_timeout(&dev->cmd_complete,
						      DAVINCI_I2C_TIMEOUT);
	dev->buf_len = 0;
	if (r < 0)
		return r;

	if (r == 0) {
		dev_err(dev->dev, "controller timed out\n");
		i2c_davinci_init(dev);
		dev->buf_len = 0;
		return -ETIMEDOUT;
	}
	if (dev->buf_len) {
		/* This should be 0 if all bytes were transferred
		 * or dev->cmd_err denotes an error.
		 * A signal may have aborted the transfer.
		 */
		if (r >= 0) {
			dev_err(dev->dev, "abnormal termination buf_len=%i\n",
				dev->buf_len);
			r = -EREMOTEIO;
		}
		dev->terminate = 1;
		wmb();
		dev->buf_len = 0;
	}
	if (r < 0)
		return r;

	/* no error */
	if (likely(!dev->cmd_err))
@@ -354,6 +370,27 @@ static u32 i2c_davinci_func(struct i2c_adapter *adap)
	return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
}

static void terminate_read(struct davinci_i2c_dev *dev)
{
	u16 w = davinci_i2c_read_reg(dev, DAVINCI_I2C_MDR_REG);
	w |= DAVINCI_I2C_MDR_NACK;
	davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, w);

	/* Throw away data */
	davinci_i2c_read_reg(dev, DAVINCI_I2C_DRR_REG);
	if (!dev->terminate)
		dev_err(dev->dev, "RDR IRQ while no data requested\n");
}
static void terminate_write(struct davinci_i2c_dev *dev)
{
	u16 w = davinci_i2c_read_reg(dev, DAVINCI_I2C_MDR_REG);
	w |= DAVINCI_I2C_MDR_RM | DAVINCI_I2C_MDR_STP;
	davinci_i2c_write_reg(dev, DAVINCI_I2C_MDR_REG, w);

	if (!dev->terminate)
		dev_err(dev->dev, "TDR IRQ while no data to send\n");
}

/*
 * Interrupt service routine. This gets called whenever an I2C interrupt
 * occurs.
@@ -374,12 +411,15 @@ static irqreturn_t i2c_davinci_isr(int this_irq, void *dev_id)

		switch (stat) {
		case DAVINCI_I2C_IVR_AL:
			/* Arbitration lost, must retry */
			dev->cmd_err |= DAVINCI_I2C_STR_AL;
			dev->buf_len = 0;
			complete(&dev->cmd_complete);
			break;

		case DAVINCI_I2C_IVR_NACK:
			dev->cmd_err |= DAVINCI_I2C_STR_NACK;
			dev->buf_len = 0;
			complete(&dev->cmd_complete);
			break;

@@ -401,9 +441,10 @@ static irqreturn_t i2c_davinci_isr(int this_irq, void *dev_id)
				davinci_i2c_write_reg(dev,
					DAVINCI_I2C_STR_REG,
					DAVINCI_I2C_IMR_RRDY);
			} else
				dev_err(dev->dev, "RDR IRQ while no "
					"data requested\n");
			} else {
				/* signal can terminate transfer */
				terminate_read(dev);
			}
			break;

		case DAVINCI_I2C_IVR_XRDY:
@@ -420,9 +461,10 @@ static irqreturn_t i2c_davinci_isr(int this_irq, void *dev_id)
				davinci_i2c_write_reg(dev,
						      DAVINCI_I2C_IMR_REG,
						      w);
			} else
				dev_err(dev->dev, "TDR IRQ while no data to "
					"send\n");
			} else {
				/* signal can terminate transfer */
				terminate_write(dev);
			}
			break;

		case DAVINCI_I2C_IVR_SCD: