#include <linux/notifier.h>
 #include <linux/suspend.h>
 #include <linux/acpi.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+
 #include <asm/intel_pmc_ipc.h>
+
 #include <linux/platform_data/itco_wdt.h>
 
 /*
 #define IPC_WRITE_BUFFER       0x80
 #define IPC_READ_BUFFER                0x90
 
+/* PMC Global Control Registers */
+#define GCR_TELEM_DEEP_S0IX_OFFSET     0x1078
+#define GCR_TELEM_SHLW_S0IX_OFFSET     0x1080
+
+/* Residency with clock rate at 19.2MHz to usecs */
+#define S0IX_RESIDENCY_IN_USECS(d, s)          \
+({                                             \
+       u64 result = 10ull * ((d) + (s));       \
+       do_div(result, 192);                    \
+       result;                                 \
+})
+
 /*
  * 16-byte buffer for sending data associated with IPC command.
  */
 #define PLAT_RESOURCE_IPC_INDEX                0
 #define PLAT_RESOURCE_IPC_SIZE         0x1000
 #define PLAT_RESOURCE_GCR_OFFSET       0x1008
-#define PLAT_RESOURCE_GCR_SIZE         0x4
+#define PLAT_RESOURCE_GCR_SIZE         0x1000
 #define PLAT_RESOURCE_BIOS_DATA_INDEX  1
 #define PLAT_RESOURCE_BIOS_IFACE_INDEX 2
 #define PLAT_RESOURCE_TELEM_SSRAM_INDEX        3
        /* gcr */
        resource_size_t gcr_base;
        int gcr_size;
+       bool has_gcr_regs;
 
        /* punit */
        struct platform_device *punit_dev;
        return readl(ipcdev.ipc_base + IPC_READ_BUFFER + offset);
 }
 
+static inline u64 gcr_data_readq(u32 offset)
+{
+       return readq(ipcdev.ipc_base + offset);
+}
+
 static int intel_pmc_ipc_check_status(void)
 {
        int status;
                dev_err(&pdev->dev, "Failed to get ipc resource\n");
                return -ENXIO;
        }
-       size = PLAT_RESOURCE_IPC_SIZE;
+       size = PLAT_RESOURCE_IPC_SIZE + PLAT_RESOURCE_GCR_SIZE;
+
        if (!request_mem_region(res->start, size, pdev->name)) {
                dev_err(&pdev->dev, "Failed to request ipc resource\n");
                return -EBUSY;
        return 0;
 }
 
+/**
+ * intel_pmc_s0ix_counter_read() - Read S0ix residency.
+ * @data: Out param that contains current S0ix residency count.
+ *
+ * Return: an error code or 0 on success.
+ */
+int intel_pmc_s0ix_counter_read(u64 *data)
+{
+       u64 deep, shlw;
+
+       if (!ipcdev.has_gcr_regs)
+               return -EACCES;
+
+       deep = gcr_data_readq(GCR_TELEM_DEEP_S0IX_OFFSET);
+       shlw = gcr_data_readq(GCR_TELEM_SHLW_S0IX_OFFSET);
+
+       *data = S0IX_RESIDENCY_IN_USECS(deep, shlw);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(intel_pmc_s0ix_counter_read);
+
 #ifdef CONFIG_ACPI
 static const struct acpi_device_id ipc_acpi_ids[] = {
        { "INT34D2", 0},
                goto err_sys;
        }
 
+       ipcdev.has_gcr_regs = true;
+
        return 0;
 err_sys:
        free_irq(ipcdev.irq, &ipcdev);
        iounmap(ipcdev.ipc_base);
        res = platform_get_resource(pdev, IORESOURCE_MEM,
                                    PLAT_RESOURCE_IPC_INDEX);
-       if (res)
-               release_mem_region(res->start, PLAT_RESOURCE_IPC_SIZE);
+       if (res) {
+               release_mem_region(res->start,
+                                  PLAT_RESOURCE_IPC_SIZE +
+                                  PLAT_RESOURCE_GCR_SIZE);
+       }
        return ret;
 }
 
        iounmap(ipcdev.ipc_base);
        res = platform_get_resource(pdev, IORESOURCE_MEM,
                                    PLAT_RESOURCE_IPC_INDEX);
-       if (res)
-               release_mem_region(res->start, PLAT_RESOURCE_IPC_SIZE);
+       if (res) {
+               release_mem_region(res->start,
+                                  PLAT_RESOURCE_IPC_SIZE +
+                                  PLAT_RESOURCE_GCR_SIZE);
+       }
        ipcdev.dev = NULL;
        return 0;
 }