summaryrefslogtreecommitdiff
path: root/drivers/i2c/i2c-mux.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/i2c/i2c-mux.c')
-rw-r--r--drivers/i2c/i2c-mux.c112
1 files changed, 104 insertions, 8 deletions
diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c
index 774507b54b57..ce3c3ad3f129 100644
--- a/drivers/i2c/i2c-mux.c
+++ b/drivers/i2c/i2c-mux.c
@@ -36,21 +36,61 @@ struct i2c_mux_priv {
u32 chan_id;
};
+static void i2c_mux_hold(struct i2c_mux_core *muxc, unsigned long timeout)
+{
+ mutex_lock(&muxc->hold_lock);
+ schedule_delayed_work(&muxc->unhold_work, timeout);
+}
+
+static void i2c_mux_unhold(struct i2c_mux_core *muxc)
+{
+ cancel_delayed_work_sync(&muxc->unhold_work);
+ mutex_unlock(&muxc->hold_lock);
+}
+
+static void i2c_mux_unhold_work(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct i2c_mux_core *muxc = container_of(dwork, struct i2c_mux_core,
+ unhold_work);
+
+ mutex_unlock(&muxc->hold_lock);
+}
+
static int __i2c_mux_master_xfer(struct i2c_adapter *adap,
struct i2c_msg msgs[], int num)
{
struct i2c_mux_priv *priv = adap->algo_data;
struct i2c_mux_core *muxc = priv->muxc;
struct i2c_adapter *parent = muxc->parent;
+ enum i2c_hold_msg_type hold_msg;
+ unsigned long timeout;
int ret;
/* Switch to the right mux port and perform the transfer. */
+ hold_msg = i2c_check_hold_msg(msgs[num - 1].flags,
+ msgs[num - 1].len,
+ (u16 *)msgs[num - 1].buf);
+ if (hold_msg == I2C_HOLD_MSG_SET) {
+ timeout = msecs_to_jiffies(*(u16 *)msgs[num - 1].buf);
+ i2c_mux_hold(muxc, timeout);
+ } else if (hold_msg == I2C_HOLD_MSG_NONE) {
+ mutex_lock(&muxc->hold_lock);
+ }
ret = muxc->select(muxc, priv->chan_id);
if (ret >= 0)
ret = __i2c_transfer(parent, msgs, num);
- if (muxc->deselect)
- muxc->deselect(muxc, priv->chan_id);
+ if (hold_msg != I2C_HOLD_MSG_SET) {
+ if (muxc->deselect)
+ muxc->deselect(muxc, priv->chan_id);
+ if (hold_msg == I2C_HOLD_MSG_RESET)
+ i2c_mux_unhold(muxc);
+ else
+ mutex_unlock(&muxc->hold_lock);
+ } else if (hold_msg == I2C_HOLD_MSG_SET && ret < 0) {
+ i2c_mux_unhold(muxc);
+ }
return ret;
}
@@ -61,15 +101,32 @@ static int i2c_mux_master_xfer(struct i2c_adapter *adap,
struct i2c_mux_priv *priv = adap->algo_data;
struct i2c_mux_core *muxc = priv->muxc;
struct i2c_adapter *parent = muxc->parent;
+ enum i2c_hold_msg_type hold_msg;
+ unsigned long timeout;
int ret;
/* Switch to the right mux port and perform the transfer. */
+ hold_msg = i2c_check_hold_msg(msgs[num - 1].flags,
+ msgs[num - 1].len,
+ (u16 *)msgs[num - 1].buf);
+ if (hold_msg == I2C_HOLD_MSG_SET) {
+ timeout = msecs_to_jiffies(*(u16 *)msgs[num - 1].buf);
+ i2c_mux_hold(muxc, timeout);
+ } else if (hold_msg == I2C_HOLD_MSG_NONE) {
+ mutex_lock(&muxc->hold_lock);
+ }
ret = muxc->select(muxc, priv->chan_id);
if (ret >= 0)
ret = i2c_transfer(parent, msgs, num);
- if (muxc->deselect)
- muxc->deselect(muxc, priv->chan_id);
+ if (hold_msg != I2C_HOLD_MSG_SET) {
+ if (muxc->deselect)
+ muxc->deselect(muxc, priv->chan_id);
+ if (hold_msg == I2C_HOLD_MSG_RESET)
+ i2c_mux_unhold(muxc);
+ else
+ mutex_unlock(&muxc->hold_lock);
+ }
return ret;
}
@@ -82,16 +139,33 @@ static int __i2c_mux_smbus_xfer(struct i2c_adapter *adap,
struct i2c_mux_priv *priv = adap->algo_data;
struct i2c_mux_core *muxc = priv->muxc;
struct i2c_adapter *parent = muxc->parent;
+ enum i2c_hold_msg_type hold_msg;
+ unsigned long timeout;
int ret;
/* Select the right mux port and perform the transfer. */
+ hold_msg = i2c_check_hold_msg(flags,
+ size == I2C_SMBUS_WORD_DATA ? 2 : 0,
+ &data->word);
+ if (hold_msg == I2C_HOLD_MSG_SET) {
+ timeout = msecs_to_jiffies(data->word);
+ i2c_mux_hold(muxc, timeout);
+ } else if (hold_msg == I2C_HOLD_MSG_NONE) {
+ mutex_lock(&muxc->hold_lock);
+ }
ret = muxc->select(muxc, priv->chan_id);
if (ret >= 0)
ret = __i2c_smbus_xfer(parent, addr, flags,
read_write, command, size, data);
- if (muxc->deselect)
- muxc->deselect(muxc, priv->chan_id);
+ if (hold_msg != I2C_HOLD_MSG_SET) {
+ if (muxc->deselect)
+ muxc->deselect(muxc, priv->chan_id);
+ if (hold_msg == I2C_HOLD_MSG_RESET)
+ i2c_mux_unhold(muxc);
+ else
+ mutex_unlock(&muxc->hold_lock);
+ }
return ret;
}
@@ -104,16 +178,33 @@ static int i2c_mux_smbus_xfer(struct i2c_adapter *adap,
struct i2c_mux_priv *priv = adap->algo_data;
struct i2c_mux_core *muxc = priv->muxc;
struct i2c_adapter *parent = muxc->parent;
+ enum i2c_hold_msg_type hold_msg;
+ unsigned long timeout;
int ret;
/* Select the right mux port and perform the transfer. */
+ hold_msg = i2c_check_hold_msg(flags,
+ size == I2C_SMBUS_WORD_DATA ? 2 : 0,
+ &data->word);
+ if (hold_msg == I2C_HOLD_MSG_SET) {
+ timeout = msecs_to_jiffies(data->word);
+ i2c_mux_hold(muxc, timeout);
+ } else if (hold_msg == I2C_HOLD_MSG_NONE) {
+ mutex_lock(&muxc->hold_lock);
+ }
ret = muxc->select(muxc, priv->chan_id);
if (ret >= 0)
ret = i2c_smbus_xfer(parent, addr, flags,
read_write, command, size, data);
- if (muxc->deselect)
- muxc->deselect(muxc, priv->chan_id);
+ if (hold_msg != I2C_HOLD_MSG_SET) {
+ if (muxc->deselect)
+ muxc->deselect(muxc, priv->chan_id);
+ if (hold_msg == I2C_HOLD_MSG_RESET)
+ i2c_mux_unhold(muxc);
+ else
+ mutex_unlock(&muxc->hold_lock);
+ }
return ret;
}
@@ -263,6 +354,9 @@ struct i2c_mux_core *i2c_mux_alloc(struct i2c_adapter *parent,
muxc->deselect = deselect;
muxc->max_adapters = max_adapters;
+ mutex_init(&muxc->hold_lock);
+ INIT_DELAYED_WORK(&muxc->unhold_work, i2c_mux_unhold_work);
+
return muxc;
}
EXPORT_SYMBOL_GPL(i2c_mux_alloc);
@@ -441,6 +535,8 @@ void i2c_mux_del_adapters(struct i2c_mux_core *muxc)
{
char symlink_name[20];
+ i2c_mux_unhold(muxc);
+
while (muxc->num_adapters) {
struct i2c_adapter *adap = muxc->adapter[--muxc->num_adapters];
struct i2c_mux_priv *priv = adap->algo_data;