mei: me: d0i3: add d0i3 enter/exit state machine
authorAlexander Usyskin <alexander.usyskin@intel.com>
Sun, 2 Aug 2015 19:20:54 +0000 (22:20 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 4 Aug 2015 00:33:55 +0000 (17:33 -0700)
Rework mei power gating state machine to support entry and exit to and
from D0i3 power state.
The choice between legacy and D0i3 routines is conditioned on
d0i3_supported flag.

The patch introduces warning:
drivers/misc/mei/hw-me.c:901:12: warning: ‘mei_me_d0i3_enter’ defined but not used [-Wunused-function]
it will go away in consequent patch

Signed-off-by: Alexander Usyskin <alexander.usyskin@intel.com>
Signed-off-by: Tomas Winkler <tomas.winkler@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/misc/mei/hbm.c
drivers/misc/mei/hbm.h
drivers/misc/mei/hw-me.c
drivers/misc/mei/hw.h

index 21f40ce87ac83aaf2bf5d33ee600eec44a2f52a0..8eec887c8f701ce732a6278a49b1fbe298ca0635 100644 (file)
@@ -901,6 +901,17 @@ static int mei_hbm_pg_enter_res(struct mei_device *dev)
        return 0;
 }
 
+/**
+ * mei_hbm_pg_resume - process with PG resume
+ *
+ * @dev: the device structure.
+ */
+void mei_hbm_pg_resume(struct mei_device *dev)
+{
+       pm_request_resume(dev->dev);
+}
+EXPORT_SYMBOL_GPL(mei_hbm_pg_resume);
+
 /**
  * mei_hbm_pg_exit_res - PG exit response received
  *
@@ -930,7 +941,7 @@ static int mei_hbm_pg_exit_res(struct mei_device *dev)
                * Start runtime pm resume sequence to exit from PG.
                */
                dev->pg_event = MEI_PG_EVENT_RECEIVED;
-               pm_request_resume(dev->dev);
+               mei_hbm_pg_resume(dev);
                break;
        default:
                WARN(1, "hbm: pg exit response: unexpected pg event = %d\n",
index 42d66d8fc1f7735a3a92884a4ff1487af495756f..a2025a5083a39f51882195613a4bac1af8b62941 100644 (file)
@@ -54,6 +54,7 @@ int mei_hbm_cl_disconnect_rsp(struct mei_device *dev, struct mei_cl *cl);
 int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl);
 bool mei_hbm_version_is_supported(struct mei_device *dev);
 int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd);
+void mei_hbm_pg_resume(struct mei_device *dev);
 int mei_hbm_cl_notify_req(struct mei_device *dev,
                          struct mei_cl *cl, u8 request);
 
index 910af88b3214c1f0f53ff9f826ea8e5718b4b640..bce4655461587ea3774c589fcd27ccd492561dc1 100644 (file)
@@ -138,6 +138,35 @@ static inline void mei_hcsr_set(struct mei_device *dev, u32 reg)
        mei_hcsr_write(dev, reg);
 }
 
+/**
+ * mei_me_d0i3c_read - Reads 32bit data from the D0I3C register
+ *
+ * @dev: the device structure
+ *
+ * Return: H_D0I3C register value (u32)
+ */
+static inline u32 mei_me_d0i3c_read(const struct mei_device *dev)
+{
+       u32 reg;
+
+       reg = mei_me_reg_read(to_me_hw(dev), H_D0I3C);
+       trace_mei_reg_read(dev->dev, "H_D0I3C", H_CSR, reg);
+
+       return reg;
+}
+
+/**
+ * mei_me_d0i3c_write - writes H_D0I3C register to device
+ *
+ * @dev: the device structure
+ * @reg: new register value
+ */
+static inline void mei_me_d0i3c_write(struct mei_device *dev, u32 reg)
+{
+       trace_mei_reg_write(dev->dev, "H_D0I3C", H_CSR, reg);
+       mei_me_reg_write(to_me_hw(dev), H_D0I3C, reg);
+}
+
 /**
  * mei_me_fw_status - read fw status register from pci config space
  *
@@ -609,13 +638,13 @@ static void mei_me_pg_unset(struct mei_device *dev)
 }
 
 /**
- * mei_me_pg_enter_sync - perform pg entry procedure
+ * mei_me_pg_legacy_enter_sync - perform legacy pg entry procedure
  *
  * @dev: the device structure
  *
  * Return: 0 on success an error code otherwise
  */
-int mei_me_pg_enter_sync(struct mei_device *dev)
+static int mei_me_pg_legacy_enter_sync(struct mei_device *dev)
 {
        struct mei_me_hw *hw = to_me_hw(dev);
        unsigned long timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT);
@@ -646,13 +675,13 @@ int mei_me_pg_enter_sync(struct mei_device *dev)
 }
 
 /**
- * mei_me_pg_exit_sync - perform pg exit procedure
+ * mei_me_pg_legacy_exit_sync - perform legacy pg exit procedure
  *
  * @dev: the device structure
  *
  * Return: 0 on success an error code otherwise
  */
-int mei_me_pg_exit_sync(struct mei_device *dev)
+static int mei_me_pg_legacy_exit_sync(struct mei_device *dev)
 {
        struct mei_me_hw *hw = to_me_hw(dev);
        unsigned long timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT);
@@ -720,8 +749,12 @@ static bool mei_me_pg_in_transition(struct mei_device *dev)
  */
 static bool mei_me_pg_is_enabled(struct mei_device *dev)
 {
+       struct mei_me_hw *hw = to_me_hw(dev);
        u32 reg = mei_me_mecsr_read(dev);
 
+       if (hw->d0i3_supported)
+               return true;
+
        if ((reg & ME_PGIC_HRA) == 0)
                goto notsupported;
 
@@ -731,7 +764,8 @@ static bool mei_me_pg_is_enabled(struct mei_device *dev)
        return true;
 
 notsupported:
-       dev_dbg(dev->dev, "pg: not supported: HGP = %d hbm version %d.%d ?= %d.%d\n",
+       dev_dbg(dev->dev, "pg: not supported: d0i3 = %d HGP = %d hbm version %d.%d ?= %d.%d\n",
+               hw->d0i3_supported,
                !!(reg & ME_PGIC_HRA),
                dev->version.major_version,
                dev->version.minor_version,
@@ -742,11 +776,211 @@ notsupported:
 }
 
 /**
- * mei_me_pg_intr - perform pg processing in interrupt thread handler
+ * mei_me_d0i3_set - write d0i3 register bit on mei device.
  *
  * @dev: the device structure
+ * @intr: ask for interrupt
+ *
+ * Return: D0I3C register value
  */
-static void mei_me_pg_intr(struct mei_device *dev)
+static u32 mei_me_d0i3_set(struct mei_device *dev, bool intr)
+{
+       u32 reg = mei_me_d0i3c_read(dev);
+
+       reg |= H_D0I3C_I3;
+       if (intr)
+               reg |= H_D0I3C_IR;
+       else
+               reg &= ~H_D0I3C_IR;
+       mei_me_d0i3c_write(dev, reg);
+       /* read it to ensure HW consistency */
+       reg = mei_me_d0i3c_read(dev);
+       return reg;
+}
+
+/**
+ * mei_me_d0i3_unset - clean d0i3 register bit on mei device.
+ *
+ * @dev: the device structure
+ *
+ * Return: D0I3C register value
+ */
+static u32 mei_me_d0i3_unset(struct mei_device *dev)
+{
+       u32 reg = mei_me_d0i3c_read(dev);
+
+       reg &= ~H_D0I3C_I3;
+       reg |= H_D0I3C_IR;
+       mei_me_d0i3c_write(dev, reg);
+       /* read it to ensure HW consistency */
+       reg = mei_me_d0i3c_read(dev);
+       return reg;
+}
+
+/**
+ * mei_me_d0i3_enter_sync - perform d0i3 entry procedure
+ *
+ * @dev: the device structure
+ *
+ * Return: 0 on success an error code otherwise
+ */
+static int mei_me_d0i3_enter_sync(struct mei_device *dev)
+{
+       struct mei_me_hw *hw = to_me_hw(dev);
+       unsigned long d0i3_timeout = mei_secs_to_jiffies(MEI_D0I3_TIMEOUT);
+       unsigned long pgi_timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT);
+       int ret;
+       u32 reg;
+
+       reg = mei_me_d0i3c_read(dev);
+       if (reg & H_D0I3C_I3) {
+               /* we are in d0i3, nothing to do */
+               dev_dbg(dev->dev, "d0i3 set not needed\n");
+               ret = 0;
+               goto on;
+       }
+
+       /* PGI entry procedure */
+       dev->pg_event = MEI_PG_EVENT_WAIT;
+
+       ret = mei_hbm_pg(dev, MEI_PG_ISOLATION_ENTRY_REQ_CMD);
+       if (ret)
+               /* FIXME: should we reset here? */
+               goto out;
+
+       mutex_unlock(&dev->device_lock);
+       wait_event_timeout(dev->wait_pg,
+               dev->pg_event == MEI_PG_EVENT_RECEIVED, pgi_timeout);
+       mutex_lock(&dev->device_lock);
+
+       if (dev->pg_event != MEI_PG_EVENT_RECEIVED) {
+               ret = -ETIME;
+               goto out;
+       }
+       /* end PGI entry procedure */
+
+       dev->pg_event = MEI_PG_EVENT_INTR_WAIT;
+
+       reg = mei_me_d0i3_set(dev, true);
+       if (!(reg & H_D0I3C_CIP)) {
+               dev_dbg(dev->dev, "d0i3 enter wait not needed\n");
+               ret = 0;
+               goto on;
+       }
+
+       mutex_unlock(&dev->device_lock);
+       wait_event_timeout(dev->wait_pg,
+               dev->pg_event == MEI_PG_EVENT_INTR_RECEIVED, d0i3_timeout);
+       mutex_lock(&dev->device_lock);
+
+       if (dev->pg_event != MEI_PG_EVENT_INTR_RECEIVED) {
+               reg = mei_me_d0i3c_read(dev);
+               if (!(reg & H_D0I3C_I3)) {
+                       ret = -ETIME;
+                       goto out;
+               }
+       }
+
+       ret = 0;
+on:
+       hw->pg_state = MEI_PG_ON;
+out:
+       dev->pg_event = MEI_PG_EVENT_IDLE;
+       dev_dbg(dev->dev, "d0i3 enter ret = %d\n", ret);
+       return ret;
+}
+
+/**
+ * mei_me_d0i3_enter - perform d0i3 entry procedure
+ *   no hbm PG handshake
+ *   no waiting for confirmation; runs with interrupts
+ *   disabled
+ *
+ * @dev: the device structure
+ *
+ * Return: 0 on success an error code otherwise
+ */
+static int mei_me_d0i3_enter(struct mei_device *dev)
+{
+       struct mei_me_hw *hw = to_me_hw(dev);
+       u32 reg;
+
+       reg = mei_me_d0i3c_read(dev);
+       if (reg & H_D0I3C_I3) {
+               /* we are in d0i3, nothing to do */
+               dev_dbg(dev->dev, "already d0i3 : set not needed\n");
+               goto on;
+       }
+
+       mei_me_d0i3_set(dev, false);
+on:
+       hw->pg_state = MEI_PG_ON;
+       dev->pg_event = MEI_PG_EVENT_IDLE;
+       dev_dbg(dev->dev, "d0i3 enter\n");
+       return 0;
+}
+
+/**
+ * mei_me_d0i3_exit_sync - perform d0i3 exit procedure
+ *
+ * @dev: the device structure
+ *
+ * Return: 0 on success an error code otherwise
+ */
+static int mei_me_d0i3_exit_sync(struct mei_device *dev)
+{
+       struct mei_me_hw *hw = to_me_hw(dev);
+       unsigned long timeout = mei_secs_to_jiffies(MEI_D0I3_TIMEOUT);
+       int ret;
+       u32 reg;
+
+       dev->pg_event = MEI_PG_EVENT_INTR_WAIT;
+
+       reg = mei_me_d0i3c_read(dev);
+       if (!(reg & H_D0I3C_I3)) {
+               /* we are not in d0i3, nothing to do */
+               dev_dbg(dev->dev, "d0i3 exit not needed\n");
+               ret = 0;
+               goto off;
+       }
+
+       reg = mei_me_d0i3_unset(dev);
+       if (!(reg & H_D0I3C_CIP)) {
+               dev_dbg(dev->dev, "d0i3 exit wait not needed\n");
+               ret = 0;
+               goto off;
+       }
+
+       mutex_unlock(&dev->device_lock);
+       wait_event_timeout(dev->wait_pg,
+               dev->pg_event == MEI_PG_EVENT_INTR_RECEIVED, timeout);
+       mutex_lock(&dev->device_lock);
+
+       if (dev->pg_event != MEI_PG_EVENT_INTR_RECEIVED) {
+               reg = mei_me_d0i3c_read(dev);
+               if (reg & H_D0I3C_I3) {
+                       ret = -ETIME;
+                       goto out;
+               }
+       }
+
+       ret = 0;
+off:
+       hw->pg_state = MEI_PG_OFF;
+out:
+       dev->pg_event = MEI_PG_EVENT_IDLE;
+
+       dev_dbg(dev->dev, "d0i3 exit ret = %d\n", ret);
+       return ret;
+}
+
+/**
+ * mei_me_pg_legacy_intr - perform legacy pg processing
+ *                        in interrupt thread handler
+ *
+ * @dev: the device structure
+ */
+static void mei_me_pg_legacy_intr(struct mei_device *dev)
 {
        struct mei_me_hw *hw = to_me_hw(dev);
 
@@ -759,6 +993,95 @@ static void mei_me_pg_intr(struct mei_device *dev)
                wake_up(&dev->wait_pg);
 }
 
+/**
+ * mei_me_d0i3_intr - perform d0i3 processing in interrupt thread handler
+ *
+ * @dev: the device structure
+ */
+static void mei_me_d0i3_intr(struct mei_device *dev)
+{
+       struct mei_me_hw *hw = to_me_hw(dev);
+
+       if (dev->pg_event == MEI_PG_EVENT_INTR_WAIT &&
+           (hw->intr_source & H_D0I3C_IS)) {
+               dev->pg_event = MEI_PG_EVENT_INTR_RECEIVED;
+               if (hw->pg_state == MEI_PG_ON) {
+                       hw->pg_state = MEI_PG_OFF;
+                       if (dev->hbm_state != MEI_HBM_IDLE) {
+                               /*
+                                * force H_RDY because it could be
+                                * wiped off during PG
+                                */
+                               dev_dbg(dev->dev, "d0i3 set host ready\n");
+                               mei_me_host_set_ready(dev);
+                       }
+               } else {
+                       hw->pg_state = MEI_PG_ON;
+               }
+
+               wake_up(&dev->wait_pg);
+       }
+
+       if (hw->pg_state == MEI_PG_ON && (hw->intr_source & H_IS)) {
+               /*
+                * HW sent some data and we are in D0i3, so
+                * we got here because of HW initiated exit from D0i3.
+                * Start runtime pm resume sequence to exit low power state.
+                */
+               dev_dbg(dev->dev, "d0i3 want resume\n");
+               mei_hbm_pg_resume(dev);
+       }
+}
+
+/**
+ * mei_me_pg_intr - perform pg processing in interrupt thread handler
+ *
+ * @dev: the device structure
+ */
+static void mei_me_pg_intr(struct mei_device *dev)
+{
+       struct mei_me_hw *hw = to_me_hw(dev);
+
+       if (hw->d0i3_supported)
+               mei_me_d0i3_intr(dev);
+       else
+               mei_me_pg_legacy_intr(dev);
+}
+
+/**
+ * mei_me_pg_enter_sync - perform runtime pm entry procedure
+ *
+ * @dev: the device structure
+ *
+ * Return: 0 on success an error code otherwise
+ */
+int mei_me_pg_enter_sync(struct mei_device *dev)
+{
+       struct mei_me_hw *hw = to_me_hw(dev);
+
+       if (hw->d0i3_supported)
+               return mei_me_d0i3_enter_sync(dev);
+       else
+               return mei_me_pg_legacy_enter_sync(dev);
+}
+
+/**
+ * mei_me_pg_exit_sync - perform runtime pm exit procedure
+ *
+ * @dev: the device structure
+ *
+ * Return: 0 on success an error code otherwise
+ */
+int mei_me_pg_exit_sync(struct mei_device *dev)
+{
+       struct mei_me_hw *hw = to_me_hw(dev);
+
+       if (hw->d0i3_supported)
+               return mei_me_d0i3_exit_sync(dev);
+       else
+               return mei_me_pg_legacy_exit_sync(dev);
+}
+
 /**
  * mei_me_irq_quick_handler - The ISR of the MEI device
  *
index c262d0bd5bd5e23c64d694df37d4b00096eeb679..ad6fbcb27f436c00b3308c6065fe66656917dd80 100644 (file)
@@ -31,8 +31,9 @@
 #define MEI_IAMTHIF_STALL_TIMER    12  /* HPS */
 #define MEI_IAMTHIF_READ_TIMER     10  /* HPS */
 
-#define MEI_PGI_TIMEOUT            1  /* PG Isolation time response 1 sec */
-#define MEI_HBM_TIMEOUT            1   /* 1 second */
+#define MEI_PGI_TIMEOUT             1  /* PG Isolation time response 1 sec */
+#define MEI_D0I3_TIMEOUT            5  /* D0i3 set/unset max response time */
+#define MEI_HBM_TIMEOUT             1  /* 1 second */
 
 /*
  * MEI Version