tpm_i2c_infineon: Add reboot handler

On gfsc100 the I2C bus sometimes does not work at all after a reboot.
The problem happens when the reboot occurs in midst of an I2C read
transfer from the TPM. The TPM is not physically reset on reboots and
apparently remains in a state where it keeps holding the bus to complete
the pending read transfer.

To avoid this situation a reboot handler is added to the TPM driver. The
handler sets a flag indicating that a reboot is underway and flushes the
work queue of the I2C controller. Subsequent attempts to read or write
are aborted.

Google-Bug-Id: 23816953

Change-Id: I58c5c6cd4485315b2850f731e1fb28a53d13cac4
diff --git a/drivers/char/tpm/tpm_i2c_infineon.c b/drivers/char/tpm/tpm_i2c_infineon.c
index ee41671..8aaa4f5 100644
--- a/drivers/char/tpm/tpm_i2c_infineon.c
+++ b/drivers/char/tpm/tpm_i2c_infineon.c
@@ -25,6 +25,7 @@
 #include <linux/i2c.h>
 #include <linux/module.h>
 #include <linux/moduleparam.h>
+#include <linux/reboot.h>
 #include <linux/wait.h>
 #include "tpm.h"
 
@@ -71,6 +72,8 @@
 	u8 buf[TPM_BUFSIZE + sizeof(u8)]; /* max. buffer size + addr */
 	struct tpm_chip *chip;
 	enum i2c_chip_type chip_type;
+	bool reboot_in_progress;
+	struct mutex reboot_lock;
 };
 
 static struct tpm_inf_dev tpm_dev;
@@ -117,6 +120,16 @@
 	/* Lock the adapter for the duration of the whole sequence. */
 	if (!tpm_dev.client->adapter->algo->master_xfer)
 		return -EOPNOTSUPP;
+
+	mutex_lock(&tpm_dev.reboot_lock);
+
+	if (tpm_dev.reboot_in_progress) {
+		dev_warn(tpm_dev.chip->dev, "reboot in progress, aborting %s\n",
+				 __func__);
+		mutex_unlock(&tpm_dev.reboot_lock);
+		return -EIO;
+        }
+
 	i2c_lock_adapter(tpm_dev.client->adapter);
 
 	if (tpm_dev.chip_type == SLB9645) {
@@ -159,6 +172,9 @@
 
 out:
 	i2c_unlock_adapter(tpm_dev.client->adapter);
+
+	mutex_unlock(&tpm_dev.reboot_lock);
+
 	/* take care of 'guard time' */
 	usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI);
 
@@ -190,6 +206,13 @@
 
 	if (!tpm_dev.client->adapter->algo->master_xfer)
 		return -EOPNOTSUPP;
+
+	if (tpm_dev.reboot_in_progress) {
+		dev_warn(tpm_dev.chip->dev, "reboot in progress, aborting %s\n",
+			__func__);
+		return -EIO;
+	}
+
 	i2c_lock_adapter(tpm_dev.client->adapter);
 
 	/* prepend the 'register address' to the buffer */
@@ -726,6 +749,22 @@
 MODULE_DEVICE_TABLE(of, tpm_tis_i2c_of_match);
 #endif
 
+static int tpm_reboot_handler(struct notifier_block *nb,unsigned long action, void *data)
+{
+	mutex_lock(&tpm_dev.reboot_lock);
+
+	tpm_dev.reboot_in_progress = true;
+	flush_work_sync(&tpm_dev.chip->work);
+
+	mutex_unlock(&tpm_dev.reboot_lock);
+
+	return 0;
+}
+
+static struct notifier_block tpm_reboot_notifier = {
+	.notifier_call  = tpm_reboot_handler
+};
+
 static SIMPLE_DEV_PM_OPS(tpm_tis_i2c_ops, tpm_pm_suspend, tpm_pm_resume);
 
 static int tpm_tis_i2c_probe(struct i2c_client *client,
@@ -744,6 +783,8 @@
 		return -ENODEV;
 	}
 
+	mutex_init(&tpm_dev.reboot_lock);
+
 	client->driver = &tpm_tis_i2c_driver;
 	tpm_dev.client = client;
 	rc = tpm_tis_i2c_init(&client->dev);
@@ -752,6 +793,10 @@
 		tpm_dev.client = NULL;
 		rc = -ENODEV;
 	}
+
+	tpm_dev.reboot_in_progress = false;
+	register_reboot_notifier(&tpm_reboot_notifier);
+
 	return rc;
 }