i2c: owl: Add support for atomic transfers
Atomic transfers are required to properly power off a machine through an I2C controlled PMIC, such as the Actions Semi ATC260x series. System shutdown may happen with interrupts being disabled and, as a consequence, the kernel may hang if the driver does not support atomic transfers. This functionality is essentially implemented by polling the FIFO Status register until either Command Execute Completed or NACK Error bits are set. Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@gmail.com> Reviewed-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> [wsa: rebased to current tree] Signed-off-by: Wolfram Sang <wsa@kernel.org>
This commit is contained in:
parent
46d43ee48d
commit
b8be24ec67
@ -14,6 +14,7 @@
|
|||||||
#include <linux/i2c.h>
|
#include <linux/i2c.h>
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
|
#include <linux/iopoll.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/of_device.h>
|
#include <linux/of_device.h>
|
||||||
|
|
||||||
@ -76,6 +77,7 @@
|
|||||||
#define OWL_I2C_FIFOCTL_TFR BIT(2)
|
#define OWL_I2C_FIFOCTL_TFR BIT(2)
|
||||||
|
|
||||||
/* I2Cc_FIFOSTAT Bit Mask */
|
/* I2Cc_FIFOSTAT Bit Mask */
|
||||||
|
#define OWL_I2C_FIFOSTAT_CECB BIT(0)
|
||||||
#define OWL_I2C_FIFOSTAT_RNB BIT(1)
|
#define OWL_I2C_FIFOSTAT_RNB BIT(1)
|
||||||
#define OWL_I2C_FIFOSTAT_RFE BIT(2)
|
#define OWL_I2C_FIFOSTAT_RFE BIT(2)
|
||||||
#define OWL_I2C_FIFOSTAT_TFF BIT(5)
|
#define OWL_I2C_FIFOSTAT_TFF BIT(5)
|
||||||
@ -83,7 +85,8 @@
|
|||||||
#define OWL_I2C_FIFOSTAT_RFD GENMASK(15, 8)
|
#define OWL_I2C_FIFOSTAT_RFD GENMASK(15, 8)
|
||||||
|
|
||||||
/* I2C bus timeout */
|
/* I2C bus timeout */
|
||||||
#define OWL_I2C_TIMEOUT msecs_to_jiffies(4 * 1000)
|
#define OWL_I2C_TIMEOUT_MS (4 * 1000)
|
||||||
|
#define OWL_I2C_TIMEOUT msecs_to_jiffies(OWL_I2C_TIMEOUT_MS)
|
||||||
|
|
||||||
#define OWL_I2C_MAX_RETRIES 50
|
#define OWL_I2C_MAX_RETRIES 50
|
||||||
|
|
||||||
@ -161,14 +164,11 @@ static void owl_i2c_set_freq(struct owl_i2c_dev *i2c_dev)
|
|||||||
writel(OWL_I2C_DIV_FACTOR(val), i2c_dev->base + OWL_I2C_REG_CLKDIV);
|
writel(OWL_I2C_DIV_FACTOR(val), i2c_dev->base + OWL_I2C_REG_CLKDIV);
|
||||||
}
|
}
|
||||||
|
|
||||||
static irqreturn_t owl_i2c_interrupt(int irq, void *_dev)
|
static void owl_i2c_xfer_data(struct owl_i2c_dev *i2c_dev)
|
||||||
{
|
{
|
||||||
struct owl_i2c_dev *i2c_dev = _dev;
|
|
||||||
struct i2c_msg *msg = i2c_dev->msg;
|
struct i2c_msg *msg = i2c_dev->msg;
|
||||||
unsigned int stat, fifostat;
|
unsigned int stat, fifostat;
|
||||||
|
|
||||||
spin_lock(&i2c_dev->lock);
|
|
||||||
|
|
||||||
i2c_dev->err = 0;
|
i2c_dev->err = 0;
|
||||||
|
|
||||||
/* Handle NACK from slave */
|
/* Handle NACK from slave */
|
||||||
@ -178,7 +178,7 @@ static irqreturn_t owl_i2c_interrupt(int irq, void *_dev)
|
|||||||
/* Clear NACK error bit by writing "1" */
|
/* Clear NACK error bit by writing "1" */
|
||||||
owl_i2c_update_reg(i2c_dev->base + OWL_I2C_REG_FIFOSTAT,
|
owl_i2c_update_reg(i2c_dev->base + OWL_I2C_REG_FIFOSTAT,
|
||||||
OWL_I2C_FIFOSTAT_RNB, true);
|
OWL_I2C_FIFOSTAT_RNB, true);
|
||||||
goto stop;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle bus error */
|
/* Handle bus error */
|
||||||
@ -188,7 +188,7 @@ static irqreturn_t owl_i2c_interrupt(int irq, void *_dev)
|
|||||||
/* Clear BUS error bit by writing "1" */
|
/* Clear BUS error bit by writing "1" */
|
||||||
owl_i2c_update_reg(i2c_dev->base + OWL_I2C_REG_STAT,
|
owl_i2c_update_reg(i2c_dev->base + OWL_I2C_REG_STAT,
|
||||||
OWL_I2C_STAT_BEB, true);
|
OWL_I2C_STAT_BEB, true);
|
||||||
goto stop;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle FIFO read */
|
/* Handle FIFO read */
|
||||||
@ -206,8 +206,16 @@ static irqreturn_t owl_i2c_interrupt(int irq, void *_dev)
|
|||||||
i2c_dev->base + OWL_I2C_REG_TXDAT);
|
i2c_dev->base + OWL_I2C_REG_TXDAT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static irqreturn_t owl_i2c_interrupt(int irq, void *_dev)
|
||||||
|
{
|
||||||
|
struct owl_i2c_dev *i2c_dev = _dev;
|
||||||
|
|
||||||
|
spin_lock(&i2c_dev->lock);
|
||||||
|
|
||||||
|
owl_i2c_xfer_data(i2c_dev);
|
||||||
|
|
||||||
stop:
|
|
||||||
/* Clear pending interrupts */
|
/* Clear pending interrupts */
|
||||||
owl_i2c_update_reg(i2c_dev->base + OWL_I2C_REG_STAT,
|
owl_i2c_update_reg(i2c_dev->base + OWL_I2C_REG_STAT,
|
||||||
OWL_I2C_STAT_IRQP, true);
|
OWL_I2C_STAT_IRQP, true);
|
||||||
@ -240,8 +248,8 @@ static int owl_i2c_check_bus_busy(struct i2c_adapter *adap)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int owl_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
|
static int owl_i2c_xfer_common(struct i2c_adapter *adap, struct i2c_msg *msgs,
|
||||||
int num)
|
int num, bool atomic)
|
||||||
{
|
{
|
||||||
struct owl_i2c_dev *i2c_dev = i2c_get_adapdata(adap);
|
struct owl_i2c_dev *i2c_dev = i2c_get_adapdata(adap);
|
||||||
struct i2c_msg *msg;
|
struct i2c_msg *msg;
|
||||||
@ -285,11 +293,12 @@ static int owl_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
|
|||||||
goto err_exit;
|
goto err_exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
reinit_completion(&i2c_dev->msg_complete);
|
if (!atomic)
|
||||||
|
reinit_completion(&i2c_dev->msg_complete);
|
||||||
|
|
||||||
/* Enable I2C controller interrupt */
|
/* Enable/disable I2C controller interrupt */
|
||||||
owl_i2c_update_reg(i2c_dev->base + OWL_I2C_REG_CTL,
|
owl_i2c_update_reg(i2c_dev->base + OWL_I2C_REG_CTL,
|
||||||
OWL_I2C_CTL_IRQE, true);
|
OWL_I2C_CTL_IRQE, !atomic);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Select: FIFO enable, Master mode, Stop enable, Data count enable,
|
* Select: FIFO enable, Master mode, Stop enable, Data count enable,
|
||||||
@ -357,20 +366,33 @@ static int owl_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
|
|||||||
|
|
||||||
spin_unlock_irqrestore(&i2c_dev->lock, flags);
|
spin_unlock_irqrestore(&i2c_dev->lock, flags);
|
||||||
|
|
||||||
time_left = wait_for_completion_timeout(&i2c_dev->msg_complete,
|
if (atomic) {
|
||||||
adap->timeout);
|
/* Wait for Command Execute Completed or NACK Error bits */
|
||||||
|
ret = readl_poll_timeout_atomic(i2c_dev->base + OWL_I2C_REG_FIFOSTAT,
|
||||||
|
val, val & (OWL_I2C_FIFOSTAT_CECB |
|
||||||
|
OWL_I2C_FIFOSTAT_RNB),
|
||||||
|
10, OWL_I2C_TIMEOUT_MS * 1000);
|
||||||
|
} else {
|
||||||
|
time_left = wait_for_completion_timeout(&i2c_dev->msg_complete,
|
||||||
|
adap->timeout);
|
||||||
|
if (!time_left)
|
||||||
|
ret = -ETIMEDOUT;
|
||||||
|
}
|
||||||
|
|
||||||
spin_lock_irqsave(&i2c_dev->lock, flags);
|
spin_lock_irqsave(&i2c_dev->lock, flags);
|
||||||
if (time_left == 0) {
|
|
||||||
|
if (ret) {
|
||||||
dev_err(&adap->dev, "Transaction timed out\n");
|
dev_err(&adap->dev, "Transaction timed out\n");
|
||||||
/* Send stop condition and release the bus */
|
/* Send stop condition and release the bus */
|
||||||
owl_i2c_update_reg(i2c_dev->base + OWL_I2C_REG_CTL,
|
owl_i2c_update_reg(i2c_dev->base + OWL_I2C_REG_CTL,
|
||||||
OWL_I2C_CTL_GBCC_STOP | OWL_I2C_CTL_RB,
|
OWL_I2C_CTL_GBCC_STOP | OWL_I2C_CTL_RB,
|
||||||
true);
|
true);
|
||||||
ret = -ETIMEDOUT;
|
|
||||||
goto err_exit;
|
goto err_exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (atomic)
|
||||||
|
owl_i2c_xfer_data(i2c_dev);
|
||||||
|
|
||||||
ret = i2c_dev->err < 0 ? i2c_dev->err : num;
|
ret = i2c_dev->err < 0 ? i2c_dev->err : num;
|
||||||
|
|
||||||
err_exit:
|
err_exit:
|
||||||
@ -384,9 +406,22 @@ unlocked_err_exit:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int owl_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
|
||||||
|
int num)
|
||||||
|
{
|
||||||
|
return owl_i2c_xfer_common(adap, msgs, num, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int owl_i2c_xfer_atomic(struct i2c_adapter *adap,
|
||||||
|
struct i2c_msg *msgs, int num)
|
||||||
|
{
|
||||||
|
return owl_i2c_xfer_common(adap, msgs, num, true);
|
||||||
|
}
|
||||||
|
|
||||||
static const struct i2c_algorithm owl_i2c_algorithm = {
|
static const struct i2c_algorithm owl_i2c_algorithm = {
|
||||||
.master_xfer = owl_i2c_master_xfer,
|
.master_xfer = owl_i2c_xfer,
|
||||||
.functionality = owl_i2c_func,
|
.master_xfer_atomic = owl_i2c_xfer_atomic,
|
||||||
|
.functionality = owl_i2c_func,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct i2c_adapter_quirks owl_i2c_quirks = {
|
static const struct i2c_adapter_quirks owl_i2c_quirks = {
|
||||||
|
Loading…
Reference in New Issue
Block a user