Merge branch 'fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/linux-arm-soc
[pandora-kernel.git] / drivers / char / tpm / tpm_tis.c
index b97ce2b..7fc2f10 100644 (file)
@@ -80,7 +80,7 @@ enum tis_defaults {
 static LIST_HEAD(tis_chips);
 static DEFINE_SPINLOCK(tis_lock);
 
-#ifdef CONFIG_ACPI
+#ifdef CONFIG_PNP
 static int is_itpm(struct pnp_dev *dev)
 {
        struct acpi_device *acpi = pnp_acpi_device(dev);
@@ -93,11 +93,6 @@ static int is_itpm(struct pnp_dev *dev)
 
        return 0;
 }
-#else
-static int is_itpm(struct pnp_dev *dev)
-{
-       return 0;
-}
 #endif
 
 static int check_locality(struct tpm_chip *chip, int l)
@@ -306,11 +301,10 @@ MODULE_PARM_DESC(itpm, "Force iTPM workarounds (found on some Lenovo laptops)");
  * tpm.c can skip polling for the data to be available as the interrupt is
  * waited for here
  */
-static int tpm_tis_send(struct tpm_chip *chip, u8 *buf, size_t len)
+static int tpm_tis_send_data(struct tpm_chip *chip, u8 *buf, size_t len)
 {
        int rc, status, burstcnt;
        size_t count = 0;
-       u32 ordinal;
 
        if (request_locality(chip, 0) < 0)
                return -EBUSY;
@@ -345,8 +339,7 @@ static int tpm_tis_send(struct tpm_chip *chip, u8 *buf, size_t len)
 
        /* write last byte */
        iowrite8(buf[count],
-                chip->vendor.iobase +
-                TPM_DATA_FIFO(chip->vendor.locality));
+                chip->vendor.iobase + TPM_DATA_FIFO(chip->vendor.locality));
        wait_for_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c,
                      &chip->vendor.int_queue);
        status = tpm_tis_status(chip);
@@ -355,6 +348,28 @@ static int tpm_tis_send(struct tpm_chip *chip, u8 *buf, size_t len)
                goto out_err;
        }
 
+       return 0;
+
+out_err:
+       tpm_tis_ready(chip);
+       release_locality(chip, chip->vendor.locality, 0);
+       return rc;
+}
+
+/*
+ * If interrupts are used (signaled by an irq set in the vendor structure)
+ * tpm.c can skip polling for the data to be available as the interrupt is
+ * waited for here
+ */
+static int tpm_tis_send(struct tpm_chip *chip, u8 *buf, size_t len)
+{
+       int rc;
+       u32 ordinal;
+
+       rc = tpm_tis_send_data(chip, buf, len);
+       if (rc < 0)
+               return rc;
+
        /* go and do it */
        iowrite8(TPM_STS_GO,
                 chip->vendor.iobase + TPM_STS(chip->vendor.locality));
@@ -376,6 +391,47 @@ out_err:
        return rc;
 }
 
+/*
+ * Early probing for iTPM with STS_DATA_EXPECT flaw.
+ * Try sending command without itpm flag set and if that
+ * fails, repeat with itpm flag set.
+ */
+static int probe_itpm(struct tpm_chip *chip)
+{
+       int rc = 0;
+       u8 cmd_getticks[] = {
+               0x00, 0xc1, 0x00, 0x00, 0x00, 0x0a,
+               0x00, 0x00, 0x00, 0xf1
+       };
+       size_t len = sizeof(cmd_getticks);
+       int rem_itpm = itpm;
+
+       itpm = 0;
+
+       rc = tpm_tis_send_data(chip, cmd_getticks, len);
+       if (rc == 0)
+               goto out;
+
+       tpm_tis_ready(chip);
+       release_locality(chip, chip->vendor.locality, 0);
+
+       itpm = 1;
+
+       rc = tpm_tis_send_data(chip, cmd_getticks, len);
+       if (rc == 0) {
+               dev_info(chip->dev, "Detected an iTPM.\n");
+               rc = 1;
+       } else
+               rc = -EFAULT;
+
+out:
+       itpm = rem_itpm;
+       tpm_tis_ready(chip);
+       release_locality(chip, chip->vendor.locality, 0);
+
+       return rc;
+}
+
 static const struct file_operations tis_ops = {
        .owner = THIS_MODULE,
        .llseek = no_llseek,
@@ -438,7 +494,7 @@ static irqreturn_t tis_int_probe(int irq, void *dev_id)
        if (interrupt == 0)
                return IRQ_NONE;
 
-       chip->vendor.irq = irq;
+       chip->vendor.probed_irq = irq;
 
        /* Clear interrupts handled with TPM_EOI */
        iowrite32(interrupt,
@@ -486,7 +542,7 @@ static int tpm_tis_init(struct device *dev, resource_size_t start,
                        resource_size_t len, unsigned int irq)
 {
        u32 vendor, intfcaps, intmask;
-       int rc, i;
+       int rc, i, irq_s, irq_e;
        struct tpm_chip *chip;
 
        if (!(chip = tpm_register_hardware(dev, &tpm_tis)))
@@ -515,6 +571,14 @@ static int tpm_tis_init(struct device *dev, resource_size_t start,
                 "1.2 TPM (device-id 0x%X, rev-id %d)\n",
                 vendor >> 16, ioread8(chip->vendor.iobase + TPM_RID(0)));
 
+       if (!itpm) {
+               itpm = probe_itpm(chip);
+               if (itpm < 0) {
+                       rc = -ENODEV;
+                       goto out_err;
+               }
+       }
+
        if (itpm)
                dev_info(dev, "Intel iTPM workaround enabled\n");
 
@@ -544,6 +608,9 @@ static int tpm_tis_init(struct device *dev, resource_size_t start,
        if (intfcaps & TPM_INTF_DATA_AVAIL_INT)
                dev_dbg(dev, "\tData Avail Int Support\n");
 
+       /* get the timeouts before testing for irqs */
+       tpm_get_timeouts(chip);
+
        /* INTERRUPT Setup */
        init_waitqueue_head(&chip->vendor.read_queue);
        init_waitqueue_head(&chip->vendor.int_queue);
@@ -562,13 +629,19 @@ static int tpm_tis_init(struct device *dev, resource_size_t start,
        if (interrupts)
                chip->vendor.irq = irq;
        if (interrupts && !chip->vendor.irq) {
-               chip->vendor.irq =
+               irq_s =
                    ioread8(chip->vendor.iobase +
                            TPM_INT_VECTOR(chip->vendor.locality));
+               if (irq_s) {
+                       irq_e = irq_s;
+               } else {
+                       irq_s = 3;
+                       irq_e = 15;
+               }
 
-               for (i = 3; i < 16 && chip->vendor.irq == 0; i++) {
+               for (i = irq_s; i <= irq_e && chip->vendor.irq == 0; i++) {
                        iowrite8(i, chip->vendor.iobase +
-                                   TPM_INT_VECTOR(chip->vendor.locality));
+                                TPM_INT_VECTOR(chip->vendor.locality));
                        if (request_irq
                            (i, tis_int_probe, IRQF_SHARED,
                             chip->vendor.miscdev.name, chip) != 0) {
@@ -590,9 +663,22 @@ static int tpm_tis_init(struct device *dev, resource_size_t start,
                                  chip->vendor.iobase +
                                  TPM_INT_ENABLE(chip->vendor.locality));
 
+                       chip->vendor.probed_irq = 0;
+
                        /* Generate Interrupts */
                        tpm_gen_interrupt(chip);
 
+                       chip->vendor.irq = chip->vendor.probed_irq;
+
+                       /* free_irq will call into tis_int_probe;
+                          clear all irqs we haven't seen while doing
+                          tpm_gen_interrupt */
+                       iowrite32(ioread32
+                                 (chip->vendor.iobase +
+                                  TPM_INT_STATUS(chip->vendor.locality)),
+                                 chip->vendor.iobase +
+                                 TPM_INT_STATUS(chip->vendor.locality));
+
                        /* Turn off */
                        iowrite32(intmask,
                                  chip->vendor.iobase +
@@ -631,7 +717,6 @@ static int tpm_tis_init(struct device *dev, resource_size_t start,
        list_add(&chip->vendor.list, &tis_chips);
        spin_unlock(&tis_lock);
 
-       tpm_get_timeouts(chip);
        tpm_continue_selftest(chip);
 
        return 0;
@@ -641,6 +726,29 @@ out_err:
        tpm_remove_hardware(chip->dev);
        return rc;
 }
+
+static void tpm_tis_reenable_interrupts(struct tpm_chip *chip)
+{
+       u32 intmask;
+
+       /* reenable interrupts that device may have lost or
+          BIOS/firmware may have disabled */
+       iowrite8(chip->vendor.irq, chip->vendor.iobase +
+                TPM_INT_VECTOR(chip->vendor.locality));
+
+       intmask =
+           ioread32(chip->vendor.iobase +
+                    TPM_INT_ENABLE(chip->vendor.locality));
+
+       intmask |= TPM_INTF_CMD_READY_INT
+           | TPM_INTF_LOCALITY_CHANGE_INT | TPM_INTF_DATA_AVAIL_INT
+           | TPM_INTF_STS_VALID_INT | TPM_GLOBAL_INT_ENABLE;
+
+       iowrite32(intmask,
+                 chip->vendor.iobase + TPM_INT_ENABLE(chip->vendor.locality));
+}
+
+
 #ifdef CONFIG_PNP
 static int __devinit tpm_tis_pnp_init(struct pnp_dev *pnp_dev,
                                      const struct pnp_device_id *pnp_id)
@@ -667,28 +775,6 @@ static int tpm_tis_pnp_suspend(struct pnp_dev *dev, pm_message_t msg)
        return tpm_pm_suspend(&dev->dev, msg);
 }
 
-static void tpm_tis_reenable_interrupts(struct tpm_chip *chip)
-{
-       u32 intmask;
-
-       /* reenable interrupts that device may have lost or
-          BIOS/firmware may have disabled */
-       iowrite8(chip->vendor.irq, chip->vendor.iobase +
-                TPM_INT_VECTOR(chip->vendor.locality));
-
-       intmask =
-           ioread32(chip->vendor.iobase +
-                    TPM_INT_ENABLE(chip->vendor.locality));
-
-       intmask |= TPM_INTF_CMD_READY_INT
-           | TPM_INTF_LOCALITY_CHANGE_INT | TPM_INTF_DATA_AVAIL_INT
-           | TPM_INTF_STS_VALID_INT | TPM_GLOBAL_INT_ENABLE;
-
-       iowrite32(intmask,
-                 chip->vendor.iobase + TPM_INT_ENABLE(chip->vendor.locality));
-}
-
-
 static int tpm_tis_pnp_resume(struct pnp_dev *dev)
 {
        struct tpm_chip *chip = pnp_get_drvdata(dev);