Merge branches 'for-4.10/asus', 'for-4.10/cp2112', 'for-4.10/i2c-hid-nopower', 'for...
[sfrench/cifs-2.6.git] / drivers / hid / intel-ish-hid / ipc / ipc.c
index e2517c11e0ee053c68ff27c5f11325bcf601ba2a..842d8416a7a6f0c8d585eacece02aa319ea20ce3 100644 (file)
@@ -19,7 +19,6 @@
 #include <linux/jiffies.h>
 #include "client.h"
 #include "hw-ish.h"
-#include "utils.h"
 #include "hbm.h"
 
 /* For FW reset flow */
@@ -310,6 +309,7 @@ static int write_ipc_from_queue(struct ishtp_device *dev)
                                                ((uint32_t)tv_utc.tv_usec);
                ts_format.ts1_source = HOST_SYSTEM_TIME_USEC;
                ts_format.ts2_source = HOST_UTC_TIME_USEC;
+               ts_format.reserved = 0;
 
                time_update.primary_host_time = usec_system;
                time_update.secondary_host_time = usec_utc;
@@ -427,6 +427,59 @@ static int ipc_send_mng_msg(struct ishtp_device *dev, uint32_t msg_code,
                sizeof(uint32_t) + size);
 }
 
+#define WAIT_FOR_FW_RDY                        0x1
+#define WAIT_FOR_INPUT_RDY             0x2
+
+/**
+ * timed_wait_for_timeout() - wait special event with timeout
+ * @dev: ISHTP device pointer
+ * @condition: indicate the condition for waiting
+ * @timeinc: time slice for every wait cycle, in ms
+ * @timeout: time in ms for timeout
+ *
+ * This function will check special event to be ready in a loop, the loop
+ * period is specificd in timeinc. Wait timeout will causes failure.
+ *
+ * Return: 0 for success else failure code
+ */
+static int timed_wait_for_timeout(struct ishtp_device *dev, int condition,
+                               unsigned int timeinc, unsigned int timeout)
+{
+       bool complete = false;
+       int ret;
+
+       do {
+               if (condition == WAIT_FOR_FW_RDY) {
+                       complete = ishtp_fw_is_ready(dev);
+               } else if (condition == WAIT_FOR_INPUT_RDY) {
+                       complete = ish_is_input_ready(dev);
+               } else {
+                       ret = -EINVAL;
+                       goto out;
+               }
+
+               if (!complete) {
+                       unsigned long left_time;
+
+                       left_time = msleep_interruptible(timeinc);
+                       timeout -= (timeinc - left_time);
+               }
+       } while (!complete && timeout > 0);
+
+       if (complete)
+               ret = 0;
+       else
+               ret = -EBUSY;
+
+out:
+       return ret;
+}
+
+#define TIME_SLICE_FOR_FW_RDY_MS               100
+#define TIME_SLICE_FOR_INPUT_RDY_MS            100
+#define TIMEOUT_FOR_FW_RDY_MS                  2000
+#define TIMEOUT_FOR_INPUT_RDY_MS               2000
+
 /**
  * ish_fw_reset_handler() - FW reset handler
  * @dev: ishtp device pointer
@@ -456,8 +509,8 @@ static int ish_fw_reset_handler(struct ishtp_device *dev)
        ishtp_reset_handler(dev);
 
        if (!ish_is_input_ready(dev))
-               timed_wait_for_timeout(WAIT_FOR_SEND_SLICE,
-                       ish_is_input_ready(dev), (2 * HZ));
+               timed_wait_for_timeout(dev, WAIT_FOR_INPUT_RDY,
+                       TIME_SLICE_FOR_INPUT_RDY_MS, TIMEOUT_FOR_INPUT_RDY_MS);
 
        /* ISH FW is dead */
        if (!ish_is_input_ready(dev))
@@ -472,8 +525,8 @@ static int ish_fw_reset_handler(struct ishtp_device *dev)
                         sizeof(uint32_t));
 
        /* Wait for ISH FW'es ILUP and ISHTP_READY */
-       timed_wait_for_timeout(WAIT_FOR_SEND_SLICE, ishtp_fw_is_ready(dev),
-               (2 * HZ));
+       timed_wait_for_timeout(dev, WAIT_FOR_FW_RDY,
+                       TIME_SLICE_FOR_FW_RDY_MS, TIMEOUT_FOR_FW_RDY_MS);
        if (!ishtp_fw_is_ready(dev)) {
                /* ISH FW is dead */
                uint32_t        ish_status;
@@ -487,6 +540,8 @@ static int ish_fw_reset_handler(struct ishtp_device *dev)
        return  0;
 }
 
+#define TIMEOUT_FOR_HW_RDY_MS                  300
+
 /**
  * ish_fw_reset_work_fn() - FW reset worker function
  * @unused: not used
@@ -500,7 +555,7 @@ static void fw_reset_work_fn(struct work_struct *unused)
        rv = ish_fw_reset_handler(ishtp_dev);
        if (!rv) {
                /* ISH is ILUP & ISHTP-ready. Restart ISHTP */
-               schedule_timeout(HZ / 3);
+               msleep_interruptible(TIMEOUT_FOR_HW_RDY_MS);
                ishtp_dev->recvd_hw_ready = 1;
                wake_up_interruptible(&ishtp_dev->wait_hw_ready);
 
@@ -637,6 +692,58 @@ eoi:
        return  IRQ_HANDLED;
 }
 
+/**
+ * ish_disable_dma() - disable dma communication between host and ISHFW
+ * @dev: ishtp device pointer
+ *
+ * Clear the dma enable bit and wait for dma inactive.
+ *
+ * Return: 0 for success else error code.
+ */
+static int ish_disable_dma(struct ishtp_device *dev)
+{
+       unsigned int    dma_delay;
+
+       /* Clear the dma enable bit */
+       ish_reg_write(dev, IPC_REG_ISH_RMP2, 0);
+
+       /* wait for dma inactive */
+       for (dma_delay = 0; dma_delay < MAX_DMA_DELAY &&
+               _ish_read_fw_sts_reg(dev) & (IPC_ISH_IN_DMA);
+               dma_delay += 5)
+               mdelay(5);
+
+       if (dma_delay >= MAX_DMA_DELAY) {
+               dev_err(dev->devc,
+                       "Wait for DMA inactive timeout\n");
+               return  -EBUSY;
+       }
+
+       return 0;
+}
+
+/**
+ * ish_wakeup() - wakeup ishfw from waiting-for-host state
+ * @dev: ishtp device pointer
+ *
+ * Set the dma enable bit and send a void message to FW,
+ * it wil wakeup FW from waiting-for-host state.
+ */
+static void ish_wakeup(struct ishtp_device *dev)
+{
+       /* Set dma enable bit */
+       ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED);
+
+       /*
+        * Send 0 IPC message so that ISH FW wakes up if it was already
+        * asleep.
+        */
+       ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT);
+
+       /* Flush writes to doorbell and REMAP2 */
+       ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
+}
+
 /**
  * _ish_hw_reset() - HW reset
  * @dev: ishtp device pointer
@@ -649,7 +756,6 @@ static int _ish_hw_reset(struct ishtp_device *dev)
 {
        struct pci_dev *pdev = dev->pdev;
        int     rv;
-       unsigned int    dma_delay;
        uint16_t csr;
 
        if (!pdev)
@@ -664,15 +770,8 @@ static int _ish_hw_reset(struct ishtp_device *dev)
                return  -EINVAL;
        }
 
-       /* Now trigger reset to FW */
-       ish_reg_write(dev, IPC_REG_ISH_RMP2, 0);
-
-       for (dma_delay = 0; dma_delay < MAX_DMA_DELAY &&
-               _ish_read_fw_sts_reg(dev) & (IPC_ISH_IN_DMA);
-               dma_delay += 5)
-               mdelay(5);
-
-       if (dma_delay >= MAX_DMA_DELAY) {
+       /* Disable dma communication between FW and host */
+       if (ish_disable_dma(dev)) {
                dev_err(&pdev->dev,
                        "Can't reset - stuck with DMA in-progress\n");
                return  -EBUSY;
@@ -690,16 +789,8 @@ static int _ish_hw_reset(struct ishtp_device *dev)
        csr |= PCI_D0;
        pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr);
 
-       ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED);
-
-       /*
-        * Send 0 IPC message so that ISH FW wakes up if it was already
-        * asleep
-        */
-       ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT);
-
-       /* Flush writes to doorbell and REMAP2 */
-       ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
+       /* Now we can enable ISH DMA operation and wakeup ISHFW */
+       ish_wakeup(dev);
 
        return  0;
 }
@@ -758,16 +849,9 @@ static int _ish_ipc_reset(struct ishtp_device *dev)
 int ish_hw_start(struct ishtp_device *dev)
 {
        ish_set_host_rdy(dev);
-       /* After that we can enable ISH DMA operation */
-       ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED);
 
-       /*
-        * Send 0 IPC message so that ISH FW wakes up if it was already
-        * asleep
-        */
-       ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT);
-       /* Flush write to doorbell */
-       ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
+       /* After that we can enable ISH DMA operation and wakeup ISHFW */
+       ish_wakeup(dev);
 
        set_host_ready(dev);
 
@@ -876,6 +960,21 @@ struct ishtp_device *ish_dev_init(struct pci_dev *pdev)
  */
 void   ish_device_disable(struct ishtp_device *dev)
 {
+       struct pci_dev *pdev = dev->pdev;
+
+       if (!pdev)
+               return;
+
+       /* Disable dma communication between FW and host */
+       if (ish_disable_dma(dev)) {
+               dev_err(&pdev->dev,
+                       "Can't reset - stuck with DMA in-progress\n");
+               return;
+       }
+
+       /* Put ISH to D3hot state for power saving */
+       pci_set_power_state(pdev, PCI_D3hot);
+
        dev->dev_state = ISHTP_DEV_DISABLED;
        ish_clr_host_rdy(dev);
 }