i2c: designware-baytrail: Disallow the CPU to enter C6 or C7 while holding the punit...
authorHans de Goede <hdegoede@redhat.com>
Fri, 10 Feb 2017 10:27:56 +0000 (11:27 +0100)
committerDaniel Vetter <daniel.vetter@ffwll.ch>
Thu, 2 Mar 2017 14:46:33 +0000 (15:46 +0100)
On my cherrytrail tablet with axp288 pmic, just doing a bunch of repeated
reads from the pmic, e.g. "i2cdump -y 14 0x34" would lookup the tablet in
1 - 3 runs guaranteed.

This seems to be causes by the cpu trying to enter C6 or C7 while we hold
the punit bus semaphore, at which point everything just hangs.

Avoid this by disallowing the CPU to enter C6 or C7 before acquiring the
punit bus semaphore.

BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=109051
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Tested-by: Takashi Iwai <tiwai@suse.de>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Acked-by: Jarkko Nikula <jarkko.nikula@linux.intel.com>
Acked-by: Wolfram Sang <wsa@the-dreams.de>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Link: http://patchwork.freedesktop.org/patch/msgid/20170210102802.20898-7-hdegoede@redhat.com
drivers/i2c/busses/i2c-designware-baytrail.c
drivers/i2c/busses/i2c-designware-core.h
drivers/i2c/busses/i2c-designware-platdrv.c

index cf022226b69347a2ce5607b3341f0098af5a26f1..650a700f215e572fccc804139a370adfc9bab9f2 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/acpi.h>
 #include <linux/i2c.h>
 #include <linux/interrupt.h>
+#include <linux/pm_qos.h>
 
 #include <asm/iosf_mbi.h>
 
@@ -56,6 +57,8 @@ static void reset_semaphore(struct dw_i2c_dev *dev)
        data &= ~PUNIT_SEMAPHORE_BIT;
        if (iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, PUNIT_SEMAPHORE, data))
                dev_err(dev->dev, "iosf failed to reset punit semaphore during write\n");
+
+       pm_qos_update_request(&dev->pm_qos, PM_QOS_DEFAULT_VALUE);
 }
 
 static int baytrail_i2c_acquire(struct dw_i2c_dev *dev)
@@ -72,11 +75,18 @@ static int baytrail_i2c_acquire(struct dw_i2c_dev *dev)
        if (!dev->release_lock)
                return 0;
 
+       /*
+        * Disallow the CPU to enter C6 or C7 state, entering these states
+        * requires the punit to talk to the pmic and if this happens while
+        * we're holding the semaphore, the SoC hangs.
+        */
+       pm_qos_update_request(&dev->pm_qos, 0);
+
        /* host driver writes to side band semaphore register */
        ret = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, PUNIT_SEMAPHORE, sem);
        if (ret) {
                dev_err(dev->dev, "iosf punit semaphore request failed\n");
-               return ret;
+               goto out;
        }
 
        /* host driver waits for bit 0 to be set in semaphore register */
@@ -95,6 +105,7 @@ static int baytrail_i2c_acquire(struct dw_i2c_dev *dev)
        } while (time_before(jiffies, end));
 
        dev_err(dev->dev, "punit semaphore timed out, resetting\n");
+out:
        reset_semaphore(dev);
 
        ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, PUNIT_SEMAPHORE, &sem);
@@ -121,7 +132,7 @@ static void baytrail_i2c_release(struct dw_i2c_dev *dev)
                jiffies_to_msecs(jiffies - acquired));
 }
 
-int i2c_dw_eval_lock_support(struct dw_i2c_dev *dev)
+int i2c_dw_probe_lock_support(struct dw_i2c_dev *dev)
 {
        acpi_status status;
        unsigned long long shared_host = 0;
@@ -149,5 +160,14 @@ int i2c_dw_eval_lock_support(struct dw_i2c_dev *dev)
        dev->release_lock = baytrail_i2c_release;
        dev->pm_runtime_disabled = true;
 
+       pm_qos_add_request(&dev->pm_qos, PM_QOS_CPU_DMA_LATENCY,
+                          PM_QOS_DEFAULT_VALUE);
+
        return 0;
 }
+
+void i2c_dw_remove_lock_support(struct dw_i2c_dev *dev)
+{
+       if (dev->acquire_lock)
+               pm_qos_remove_request(&dev->pm_qos);
+}
index 2c50571fb9c1d433098eecfd514a8645f68c02bc..94a5fd193b404a59161cd293c150758d06c89c8f 100644 (file)
@@ -23,6 +23,7 @@
  */
 
 #include <linux/i2c.h>
+#include <linux/pm_qos.h>
 
 #define DW_IC_DEFAULT_FUNCTIONALITY (I2C_FUNC_I2C |                    \
                                        I2C_FUNC_SMBUS_BYTE |           \
@@ -75,6 +76,7 @@
  * @fp_lcnt: fast plus LCNT value
  * @hs_hcnt: high speed HCNT value
  * @hs_lcnt: high speed LCNT value
+ * @pm_qos: pm_qos_request used while holding a hardware lock on the bus
  * @acquire_lock: function to acquire a hardware lock on the bus
  * @release_lock: function to release a hardware lock on the bus
  * @pm_runtime_disabled: true if pm runtime is disabled
@@ -122,6 +124,7 @@ struct dw_i2c_dev {
        u16                     fp_lcnt;
        u16                     hs_hcnt;
        u16                     hs_lcnt;
+       struct pm_qos_request   pm_qos;
        int                     (*acquire_lock)(struct dw_i2c_dev *dev);
        void                    (*release_lock)(struct dw_i2c_dev *dev);
        bool                    pm_runtime_disabled;
@@ -139,7 +142,9 @@ extern u32 i2c_dw_read_comp_param(struct dw_i2c_dev *dev);
 extern int i2c_dw_probe(struct dw_i2c_dev *dev);
 
 #if IS_ENABLED(CONFIG_I2C_DESIGNWARE_BAYTRAIL)
-extern int i2c_dw_eval_lock_support(struct dw_i2c_dev *dev);
+extern int i2c_dw_probe_lock_support(struct dw_i2c_dev *dev);
+extern void i2c_dw_remove_lock_support(struct dw_i2c_dev *dev);
 #else
-static inline int i2c_dw_eval_lock_support(struct dw_i2c_dev *dev) { return 0; }
+static inline int i2c_dw_probe_lock_support(struct dw_i2c_dev *dev) { return 0; }
+static inline void i2c_dw_remove_lock_support(struct dw_i2c_dev *dev) {}
 #endif
index 3eede7b0904bacba31d561a3b9f73976711bf302..d474db074b1a69a5277081c4f88576efd666f96a 100644 (file)
@@ -238,7 +238,7 @@ static int dw_i2c_plat_probe(struct platform_device *pdev)
                return -EINVAL;
        }
 
-       r = i2c_dw_eval_lock_support(dev);
+       r = i2c_dw_probe_lock_support(dev);
        if (r)
                return r;
 
@@ -307,6 +307,8 @@ static int dw_i2c_plat_remove(struct platform_device *pdev)
        if (!dev->pm_runtime_disabled)
                pm_runtime_disable(&pdev->dev);
 
+       i2c_dw_remove_lock_support(dev);
+
        return 0;
 }