i2c: mlxbf: support lock mechanism
[platform/kernel/linux-rpi.git] / drivers / i2c / busses / i2c-mlxbf.c
index ad5efd7..0e840eb 100644 (file)
@@ -306,6 +306,7 @@ static u64 mlxbf_i2c_corepll_frequency;
  * exact.
  */
 #define MLXBF_I2C_SMBUS_TIMEOUT   (300 * 1000) /* 300ms */
+#define MLXBF_I2C_SMBUS_LOCK_POLL_TIMEOUT (300 * 1000) /* 300ms */
 
 /* Encapsulates timing parameters. */
 struct mlxbf_i2c_timings {
@@ -514,6 +515,25 @@ static bool mlxbf_smbus_master_wait_for_idle(struct mlxbf_i2c_priv *priv)
        return false;
 }
 
+/*
+ * wait for the lock to be released before acquiring it.
+ */
+static bool mlxbf_i2c_smbus_master_lock(struct mlxbf_i2c_priv *priv)
+{
+       if (mlxbf_smbus_poll(priv->smbus->io, MLXBF_I2C_SMBUS_MASTER_GW,
+                          MLXBF_I2C_MASTER_LOCK_BIT, true,
+                          MLXBF_I2C_SMBUS_LOCK_POLL_TIMEOUT))
+               return true;
+
+       return false;
+}
+
+static void mlxbf_i2c_smbus_master_unlock(struct mlxbf_i2c_priv *priv)
+{
+       /* Clear the gw to clear the lock */
+       writel(0, priv->smbus->io + MLXBF_I2C_SMBUS_MASTER_GW);
+}
+
 static bool mlxbf_i2c_smbus_transaction_success(u32 master_status,
                                                u32 cause_status)
 {
@@ -705,10 +725,19 @@ mlxbf_i2c_smbus_start_transaction(struct mlxbf_i2c_priv *priv,
        slave = request->slave & GENMASK(6, 0);
        addr = slave << 1;
 
-       /* First of all, check whether the HW is idle. */
-       if (WARN_ON(!mlxbf_smbus_master_wait_for_idle(priv)))
+       /*
+        * Try to acquire the smbus gw lock before any reads of the GW register since
+        * a read sets the lock.
+        */
+       if (WARN_ON(!mlxbf_i2c_smbus_master_lock(priv)))
                return -EBUSY;
 
+       /* Check whether the HW is idle */
+       if (WARN_ON(!mlxbf_smbus_master_wait_for_idle(priv))) {
+               ret = -EBUSY;
+               goto out_unlock;
+       }
+
        /* Set first byte. */
        data_desc[data_idx++] = addr;
 
@@ -732,8 +761,10 @@ mlxbf_i2c_smbus_start_transaction(struct mlxbf_i2c_priv *priv,
                        write_en = 1;
                        write_len += operation->length;
                        if (data_idx + operation->length >
-                                       MLXBF_I2C_MASTER_DATA_DESC_SIZE)
-                               return -ENOBUFS;
+                                       MLXBF_I2C_MASTER_DATA_DESC_SIZE) {
+                               ret = -ENOBUFS;
+                               goto out_unlock;
+                       }
                        memcpy(data_desc + data_idx,
                               operation->buffer, operation->length);
                        data_idx += operation->length;
@@ -765,7 +796,7 @@ mlxbf_i2c_smbus_start_transaction(struct mlxbf_i2c_priv *priv,
                ret = mlxbf_i2c_smbus_enable(priv, slave, write_len, block_en,
                                         pec_en, 0);
                if (ret)
-                       return ret;
+                       goto out_unlock;
        }
 
        if (read_en) {
@@ -792,6 +823,9 @@ mlxbf_i2c_smbus_start_transaction(struct mlxbf_i2c_priv *priv,
                        priv->smbus->io + MLXBF_I2C_SMBUS_MASTER_FSM);
        }
 
+out_unlock:
+       mlxbf_i2c_smbus_master_unlock(priv);
+
        return ret;
 }