|
@@ -0,0 +1,817 @@
|
|
|
|
+/*
|
|
|
|
+ * mrst/pmu.c - driver for MRST Power Management Unit
|
|
|
|
+ *
|
|
|
|
+ * Copyright (c) 2011, Intel Corporation.
|
|
|
|
+ *
|
|
|
|
+ * This program is free software; you can redistribute it and/or modify it
|
|
|
|
+ * under the terms and conditions of the GNU General Public License,
|
|
|
|
+ * version 2, as published by the Free Software Foundation.
|
|
|
|
+ *
|
|
|
|
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
|
|
|
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
|
|
+ * more details.
|
|
|
|
+ *
|
|
|
|
+ * You should have received a copy of the GNU General Public License along with
|
|
|
|
+ * this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include <linux/cpuidle.h>
|
|
|
|
+#include <linux/debugfs.h>
|
|
|
|
+#include <linux/delay.h>
|
|
|
|
+#include <linux/interrupt.h>
|
|
|
|
+#include <linux/module.h>
|
|
|
|
+#include <linux/pci.h>
|
|
|
|
+#include <linux/seq_file.h>
|
|
|
|
+#include <linux/sfi.h>
|
|
|
|
+#include <asm/intel_scu_ipc.h>
|
|
|
|
+#include "pmu.h"
|
|
|
|
+
|
|
|
|
+#define IPCMSG_FW_REVISION 0xF4
|
|
|
|
+
|
|
|
|
+struct mrst_device {
|
|
|
|
+ u16 pci_dev_num; /* DEBUG only */
|
|
|
|
+ u16 lss;
|
|
|
|
+ u16 latest_request;
|
|
|
|
+ unsigned int pci_state_counts[PCI_D3cold + 1]; /* DEBUG only */
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * comlete list of MRST PCI devices
|
|
|
|
+ */
|
|
|
|
+static struct mrst_device mrst_devs[] = {
|
|
|
|
+/* 0 */ { 0x0800, LSS_SPI0 }, /* Moorestown SPI Ctrl 0 */
|
|
|
|
+/* 1 */ { 0x0801, LSS_SPI1 }, /* Moorestown SPI Ctrl 1 */
|
|
|
|
+/* 2 */ { 0x0802, LSS_I2C0 }, /* Moorestown I2C 0 */
|
|
|
|
+/* 3 */ { 0x0803, LSS_I2C1 }, /* Moorestown I2C 1 */
|
|
|
|
+/* 4 */ { 0x0804, LSS_I2C2 }, /* Moorestown I2C 2 */
|
|
|
|
+/* 5 */ { 0x0805, LSS_KBD }, /* Moorestown Keyboard Ctrl */
|
|
|
|
+/* 6 */ { 0x0806, LSS_USB_HC }, /* Moorestown USB Ctrl */
|
|
|
|
+/* 7 */ { 0x0807, LSS_SD_HC0 }, /* Moorestown SD Host Ctrl 0 */
|
|
|
|
+/* 8 */ { 0x0808, LSS_SD_HC1 }, /* Moorestown SD Host Ctrl 1 */
|
|
|
|
+/* 9 */ { 0x0809, LSS_NAND }, /* Moorestown NAND Ctrl */
|
|
|
|
+/* 10 */ { 0x080a, LSS_AUDIO }, /* Moorestown Audio Ctrl */
|
|
|
|
+/* 11 */ { 0x080b, LSS_IMAGING }, /* Moorestown ISP */
|
|
|
|
+/* 12 */ { 0x080c, LSS_SECURITY }, /* Moorestown Security Controller */
|
|
|
|
+/* 13 */ { 0x080d, LSS_DISPLAY }, /* Moorestown External Displays */
|
|
|
|
+/* 14 */ { 0x080e, 0 }, /* Moorestown SCU IPC */
|
|
|
|
+/* 15 */ { 0x080f, LSS_GPIO }, /* Moorestown GPIO Controller */
|
|
|
|
+/* 16 */ { 0x0810, 0 }, /* Moorestown Power Management Unit */
|
|
|
|
+/* 17 */ { 0x0811, LSS_USB_OTG }, /* Moorestown OTG Ctrl */
|
|
|
|
+/* 18 */ { 0x0812, LSS_SPI2 }, /* Moorestown SPI Ctrl 2 */
|
|
|
|
+/* 19 */ { 0x0813, 0 }, /* Moorestown SC DMA */
|
|
|
|
+/* 20 */ { 0x0814, LSS_AUDIO_LPE }, /* Moorestown LPE DMA */
|
|
|
|
+/* 21 */ { 0x0815, LSS_AUDIO_SSP }, /* Moorestown SSP0 */
|
|
|
|
+
|
|
|
|
+/* 22 */ { 0x084F, LSS_SD_HC2 }, /* Moorestown SD Host Ctrl 2 */
|
|
|
|
+
|
|
|
|
+/* 23 */ { 0x4102, 0 }, /* Lincroft */
|
|
|
|
+/* 24 */ { 0x4110, 0 }, /* Lincroft */
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/* n.b. We ignore PCI-id 0x815 in LSS9 b/c MeeGo has no driver for it */
|
|
|
|
+static u16 mrst_lss9_pci_ids[] = {0x080a, 0x0814, 0};
|
|
|
|
+static u16 mrst_lss10_pci_ids[] = {0x0800, 0x0801, 0x0802, 0x0803,
|
|
|
|
+ 0x0804, 0x0805, 0x080f, 0};
|
|
|
|
+
|
|
|
|
+/* handle concurrent SMP invokations of pmu_pci_set_power_state() */
|
|
|
|
+static spinlock_t mrst_pmu_power_state_lock;
|
|
|
|
+
|
|
|
|
+static unsigned int wake_counters[MRST_NUM_LSS]; /* DEBUG only */
|
|
|
|
+static unsigned int pmu_irq_stats[INT_INVALID + 1]; /* DEBUG only */
|
|
|
|
+
|
|
|
|
+static int graphics_is_off;
|
|
|
|
+static int lss_s0i3_enabled;
|
|
|
|
+static bool mrst_pmu_s0i3_enable;
|
|
|
|
+
|
|
|
|
+/* debug counters */
|
|
|
|
+static u32 pmu_wait_ready_calls;
|
|
|
|
+static u32 pmu_wait_ready_udelays;
|
|
|
|
+static u32 pmu_wait_ready_udelays_max;
|
|
|
|
+static u32 pmu_wait_done_calls;
|
|
|
|
+static u32 pmu_wait_done_udelays;
|
|
|
|
+static u32 pmu_wait_done_udelays_max;
|
|
|
|
+static u32 pmu_set_power_state_entry;
|
|
|
|
+static u32 pmu_set_power_state_send_cmd;
|
|
|
|
+
|
|
|
|
+static struct mrst_device *pci_id_2_mrst_dev(u16 pci_dev_num)
|
|
|
|
+{
|
|
|
|
+ int index = 0;
|
|
|
|
+
|
|
|
|
+ if ((pci_dev_num >= 0x0800) && (pci_dev_num <= 0x815))
|
|
|
|
+ index = pci_dev_num - 0x800;
|
|
|
|
+ else if (pci_dev_num == 0x084F)
|
|
|
|
+ index = 22;
|
|
|
|
+ else if (pci_dev_num == 0x4102)
|
|
|
|
+ index = 23;
|
|
|
|
+ else if (pci_dev_num == 0x4110)
|
|
|
|
+ index = 24;
|
|
|
|
+
|
|
|
|
+ if (pci_dev_num != mrst_devs[index].pci_dev_num) {
|
|
|
|
+ WARN_ONCE(1, FW_BUG "Unknown PCI device 0x%04X\n", pci_dev_num);
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return &mrst_devs[index];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * mrst_pmu_validate_cstates
|
|
|
|
+ * @dev: cpuidle_device
|
|
|
|
+ *
|
|
|
|
+ * Certain states are not appropriate for governor to pick in some cases.
|
|
|
|
+ * This function will be called as cpuidle_device's prepare callback and
|
|
|
|
+ * thus tells governor to ignore such states when selecting the next state
|
|
|
|
+ * to enter.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#define IDLE_STATE4_IS_C6 4
|
|
|
|
+#define IDLE_STATE5_IS_S0I3 5
|
|
|
|
+
|
|
|
|
+int mrst_pmu_invalid_cstates(void)
|
|
|
|
+{
|
|
|
|
+ int cpu = smp_processor_id();
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Demote to C4 if the PMU is busy.
|
|
|
|
+ * Since LSS changes leave the busy bit clear...
|
|
|
|
+ * busy means either the PMU is waiting for an ACK-C6 that
|
|
|
|
+ * isn't coming due to an MWAIT that returned immediately;
|
|
|
|
+ * or we returned from S0i3 successfully, and the PMU
|
|
|
|
+ * is not done sending us interrupts.
|
|
|
|
+ */
|
|
|
|
+ if (pmu_read_busy_status())
|
|
|
|
+ return 1 << IDLE_STATE4_IS_C6 | 1 << IDLE_STATE5_IS_S0I3;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Disallow S0i3 if: PMU is not initialized, or CPU1 is active,
|
|
|
|
+ * or if device LSS is insufficient, or the GPU is active,
|
|
|
|
+ * or if it has been explicitly disabled.
|
|
|
|
+ */
|
|
|
|
+ if (!pmu_reg || !cpumask_equal(cpu_online_mask, cpumask_of(cpu)) ||
|
|
|
|
+ !lss_s0i3_enabled || !graphics_is_off || !mrst_pmu_s0i3_enable)
|
|
|
|
+ return 1 << IDLE_STATE5_IS_S0I3;
|
|
|
|
+ else
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * pmu_update_wake_counters(): read PM_WKS, update wake_counters[]
|
|
|
|
+ * DEBUG only.
|
|
|
|
+ */
|
|
|
|
+static void pmu_update_wake_counters(void)
|
|
|
|
+{
|
|
|
|
+ int lss;
|
|
|
|
+ u32 wake_status;
|
|
|
|
+
|
|
|
|
+ wake_status = pmu_read_wks();
|
|
|
|
+
|
|
|
|
+ for (lss = 0; lss < MRST_NUM_LSS; ++lss) {
|
|
|
|
+ if (wake_status & (1 << lss))
|
|
|
|
+ wake_counters[lss]++;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+int mrst_pmu_s0i3_entry(void)
|
|
|
|
+{
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ /* Clear any possible error conditions */
|
|
|
|
+ pmu_write_ics(0x300);
|
|
|
|
+
|
|
|
|
+ /* set wake control to current D-states */
|
|
|
|
+ pmu_write_wssc(S0I3_SSS_TARGET);
|
|
|
|
+
|
|
|
|
+ status = mrst_s0i3_entry(PM_S0I3_COMMAND, &pmu_reg->pm_cmd);
|
|
|
|
+ pmu_update_wake_counters();
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* poll for maximum of 5ms for busy bit to clear */
|
|
|
|
+static int pmu_wait_ready(void)
|
|
|
|
+{
|
|
|
|
+ int udelays;
|
|
|
|
+
|
|
|
|
+ pmu_wait_ready_calls++;
|
|
|
|
+
|
|
|
|
+ for (udelays = 0; udelays < 500; ++udelays) {
|
|
|
|
+ if (udelays > pmu_wait_ready_udelays_max)
|
|
|
|
+ pmu_wait_ready_udelays_max = udelays;
|
|
|
|
+
|
|
|
|
+ if (pmu_read_busy_status() == 0)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ udelay(10);
|
|
|
|
+ pmu_wait_ready_udelays++;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * if this fires, observe
|
|
|
|
+ * /sys/kernel/debug/mrst_pmu_wait_ready_calls
|
|
|
|
+ * /sys/kernel/debug/mrst_pmu_wait_ready_udelays
|
|
|
|
+ */
|
|
|
|
+ WARN_ONCE(1, "SCU not ready for 5ms");
|
|
|
|
+ return -EBUSY;
|
|
|
|
+}
|
|
|
|
+/* poll for maximum of 50ms us for busy bit to clear */
|
|
|
|
+static int pmu_wait_done(void)
|
|
|
|
+{
|
|
|
|
+ int udelays;
|
|
|
|
+
|
|
|
|
+ pmu_wait_done_calls++;
|
|
|
|
+
|
|
|
|
+ for (udelays = 0; udelays < 500; ++udelays) {
|
|
|
|
+ if (udelays > pmu_wait_done_udelays_max)
|
|
|
|
+ pmu_wait_done_udelays_max = udelays;
|
|
|
|
+
|
|
|
|
+ if (pmu_read_busy_status() == 0)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ udelay(100);
|
|
|
|
+ pmu_wait_done_udelays++;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * if this fires, observe
|
|
|
|
+ * /sys/kernel/debug/mrst_pmu_wait_done_calls
|
|
|
|
+ * /sys/kernel/debug/mrst_pmu_wait_done_udelays
|
|
|
|
+ */
|
|
|
|
+ WARN_ONCE(1, "SCU not done for 50ms");
|
|
|
|
+ return -EBUSY;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+u32 mrst_pmu_msi_is_disabled(void)
|
|
|
|
+{
|
|
|
|
+ return pmu_msi_is_disabled();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void mrst_pmu_enable_msi(void)
|
|
|
|
+{
|
|
|
|
+ pmu_msi_enable();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * pmu_irq - pmu driver interrupt handler
|
|
|
|
+ * Context: interrupt context
|
|
|
|
+ */
|
|
|
|
+static irqreturn_t pmu_irq(int irq, void *dummy)
|
|
|
|
+{
|
|
|
|
+ union pmu_pm_ics pmu_ics;
|
|
|
|
+
|
|
|
|
+ pmu_ics.value = pmu_read_ics();
|
|
|
|
+
|
|
|
|
+ if (!pmu_ics.bits.pending)
|
|
|
|
+ return IRQ_NONE;
|
|
|
|
+
|
|
|
|
+ switch (pmu_ics.bits.cause) {
|
|
|
|
+ case INT_SPURIOUS:
|
|
|
|
+ case INT_CMD_DONE:
|
|
|
|
+ case INT_CMD_ERR:
|
|
|
|
+ case INT_WAKE_RX:
|
|
|
|
+ case INT_SS_ERROR:
|
|
|
|
+ case INT_S0IX_MISS:
|
|
|
|
+ case INT_NO_ACKC6:
|
|
|
|
+ pmu_irq_stats[pmu_ics.bits.cause]++;
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ pmu_irq_stats[INT_INVALID]++;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ pmu_write_ics(pmu_ics.value); /* Clear pending interrupt */
|
|
|
|
+
|
|
|
|
+ return IRQ_HANDLED;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Translate PCI power management to MRST LSS D-states
|
|
|
|
+ */
|
|
|
|
+static int pci_2_mrst_state(int lss, pci_power_t pci_state)
|
|
|
|
+{
|
|
|
|
+ switch (pci_state) {
|
|
|
|
+ case PCI_D0:
|
|
|
|
+ if (SSMSK(D0i1, lss) & D0I1_ACG_SSS_TARGET)
|
|
|
|
+ return D0i1;
|
|
|
|
+ else
|
|
|
|
+ return D0;
|
|
|
|
+ case PCI_D1:
|
|
|
|
+ return D0i1;
|
|
|
|
+ case PCI_D2:
|
|
|
|
+ return D0i2;
|
|
|
|
+ case PCI_D3hot:
|
|
|
|
+ case PCI_D3cold:
|
|
|
|
+ return D0i3;
|
|
|
|
+ default:
|
|
|
|
+ WARN(1, "pci_state %d\n", pci_state);
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int pmu_issue_command(u32 pm_ssc)
|
|
|
|
+{
|
|
|
|
+ union pmu_pm_set_cfg_cmd_t command;
|
|
|
|
+
|
|
|
|
+ if (pmu_read_busy_status()) {
|
|
|
|
+ pr_debug("pmu is busy, Operation not permitted\n");
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * enable interrupts in PMU so that interrupts are
|
|
|
|
+ * propagated when ioc bit for a particular set
|
|
|
|
+ * command is set
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ pmu_irq_enable();
|
|
|
|
+
|
|
|
|
+ /* Configure the sub systems for pmu2 */
|
|
|
|
+
|
|
|
|
+ pmu_write_ssc(pm_ssc);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Send the set config command for pmu its configured
|
|
|
|
+ * for mode CM_IMMEDIATE & hence with No Trigger
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ command.pmu2_params.d_param.cfg_mode = CM_IMMEDIATE;
|
|
|
|
+ command.pmu2_params.d_param.cfg_delay = 0;
|
|
|
|
+ command.pmu2_params.d_param.rsvd = 0;
|
|
|
|
+
|
|
|
|
+ /* construct the command to send SET_CFG to particular PMU */
|
|
|
|
+ command.pmu2_params.d_param.cmd = SET_CFG_CMD;
|
|
|
|
+ command.pmu2_params.d_param.ioc = 0;
|
|
|
|
+ command.pmu2_params.d_param.mode_id = 0;
|
|
|
|
+ command.pmu2_params.d_param.sys_state = SYS_STATE_S0I0;
|
|
|
|
+
|
|
|
|
+ /* write the value of PM_CMD into particular PMU */
|
|
|
|
+ pr_debug("pmu command being written %x\n",
|
|
|
|
+ command.pmu_pm_set_cfg_cmd_value);
|
|
|
|
+
|
|
|
|
+ pmu_write_cmd(command.pmu_pm_set_cfg_cmd_value);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static u16 pmu_min_lss_pci_req(u16 *ids, u16 pci_state)
|
|
|
|
+{
|
|
|
|
+ u16 existing_request;
|
|
|
|
+ int i;
|
|
|
|
+
|
|
|
|
+ for (i = 0; ids[i]; ++i) {
|
|
|
|
+ struct mrst_device *mrst_dev;
|
|
|
|
+
|
|
|
|
+ mrst_dev = pci_id_2_mrst_dev(ids[i]);
|
|
|
|
+ if (unlikely(!mrst_dev))
|
|
|
|
+ continue;
|
|
|
|
+
|
|
|
|
+ existing_request = mrst_dev->latest_request;
|
|
|
|
+ if (existing_request < pci_state)
|
|
|
|
+ pci_state = existing_request;
|
|
|
|
+ }
|
|
|
|
+ return pci_state;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * pmu_pci_set_power_state - Callback function is used by all the PCI devices
|
|
|
|
+ * for a platform specific device power on/shutdown.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+int pmu_pci_set_power_state(struct pci_dev *pdev, pci_power_t pci_state)
|
|
|
|
+{
|
|
|
|
+ u32 old_sss, new_sss;
|
|
|
|
+ int status = 0;
|
|
|
|
+ struct mrst_device *mrst_dev;
|
|
|
|
+
|
|
|
|
+ pmu_set_power_state_entry++;
|
|
|
|
+
|
|
|
|
+ BUG_ON(pdev->vendor != PCI_VENDOR_ID_INTEL);
|
|
|
|
+ BUG_ON(pci_state < PCI_D0 || pci_state > PCI_D3cold);
|
|
|
|
+
|
|
|
|
+ mrst_dev = pci_id_2_mrst_dev(pdev->device);
|
|
|
|
+ if (unlikely(!mrst_dev))
|
|
|
|
+ return -ENODEV;
|
|
|
|
+
|
|
|
|
+ mrst_dev->pci_state_counts[pci_state]++; /* count invocations */
|
|
|
|
+
|
|
|
|
+ /* PMU driver calls self as part of PCI initialization, ignore */
|
|
|
|
+ if (pdev->device == PCI_DEV_ID_MRST_PMU)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ BUG_ON(!pmu_reg); /* SW bug if called before initialized */
|
|
|
|
+
|
|
|
|
+ spin_lock(&mrst_pmu_power_state_lock);
|
|
|
|
+
|
|
|
|
+ if (pdev->d3_delay) {
|
|
|
|
+ dev_dbg(&pdev->dev, "d3_delay %d, should be 0\n",
|
|
|
|
+ pdev->d3_delay);
|
|
|
|
+ pdev->d3_delay = 0;
|
|
|
|
+ }
|
|
|
|
+ /*
|
|
|
|
+ * If Lincroft graphics, simply remember state
|
|
|
|
+ */
|
|
|
|
+ if ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY
|
|
|
|
+ && !((pdev->class & PCI_SUB_CLASS_MASK) >> 8)) {
|
|
|
|
+ if (pci_state == PCI_D0)
|
|
|
|
+ graphics_is_off = 0;
|
|
|
|
+ else
|
|
|
|
+ graphics_is_off = 1;
|
|
|
|
+ goto ret;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!mrst_dev->lss)
|
|
|
|
+ goto ret; /* device with no LSS */
|
|
|
|
+
|
|
|
|
+ if (mrst_dev->latest_request == pci_state)
|
|
|
|
+ goto ret; /* no change */
|
|
|
|
+
|
|
|
|
+ mrst_dev->latest_request = pci_state; /* record latest request */
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * LSS9 and LSS10 contain multiple PCI devices.
|
|
|
|
+ * Use the lowest numbered (highest power) state in the LSS
|
|
|
|
+ */
|
|
|
|
+ if (mrst_dev->lss == 9)
|
|
|
|
+ pci_state = pmu_min_lss_pci_req(mrst_lss9_pci_ids, pci_state);
|
|
|
|
+ else if (mrst_dev->lss == 10)
|
|
|
|
+ pci_state = pmu_min_lss_pci_req(mrst_lss10_pci_ids, pci_state);
|
|
|
|
+
|
|
|
|
+ status = pmu_wait_ready();
|
|
|
|
+ if (status)
|
|
|
|
+ goto ret;
|
|
|
|
+
|
|
|
|
+ old_sss = pmu_read_sss();
|
|
|
|
+ new_sss = old_sss & ~SSMSK(3, mrst_dev->lss);
|
|
|
|
+ new_sss |= SSMSK(pci_2_mrst_state(mrst_dev->lss, pci_state),
|
|
|
|
+ mrst_dev->lss);
|
|
|
|
+
|
|
|
|
+ if (new_sss == old_sss)
|
|
|
|
+ goto ret; /* nothing to do */
|
|
|
|
+
|
|
|
|
+ pmu_set_power_state_send_cmd++;
|
|
|
|
+
|
|
|
|
+ status = pmu_issue_command(new_sss);
|
|
|
|
+
|
|
|
|
+ if (unlikely(status != 0)) {
|
|
|
|
+ dev_err(&pdev->dev, "Failed to Issue a PM command\n");
|
|
|
|
+ goto ret;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (pmu_wait_done())
|
|
|
|
+ goto ret;
|
|
|
|
+
|
|
|
|
+ lss_s0i3_enabled =
|
|
|
|
+ ((pmu_read_sss() & S0I3_SSS_TARGET) == S0I3_SSS_TARGET);
|
|
|
|
+ret:
|
|
|
|
+ spin_unlock(&mrst_pmu_power_state_lock);
|
|
|
|
+ return status;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#ifdef CONFIG_DEBUG_FS
|
|
|
|
+static char *d0ix_names[] = {"D0", "D0i1", "D0i2", "D0i3"};
|
|
|
|
+
|
|
|
|
+static inline const char *d0ix_name(int state)
|
|
|
|
+{
|
|
|
|
+ return d0ix_names[(int) state];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int debug_mrst_pmu_show(struct seq_file *s, void *unused)
|
|
|
|
+{
|
|
|
|
+ struct pci_dev *pdev = NULL;
|
|
|
|
+ u32 cur_pmsss;
|
|
|
|
+ int lss;
|
|
|
|
+
|
|
|
|
+ seq_printf(s, "0x%08X D0I1_ACG_SSS_TARGET\n", D0I1_ACG_SSS_TARGET);
|
|
|
|
+
|
|
|
|
+ cur_pmsss = pmu_read_sss();
|
|
|
|
+
|
|
|
|
+ seq_printf(s, "0x%08X S0I3_SSS_TARGET\n", S0I3_SSS_TARGET);
|
|
|
|
+
|
|
|
|
+ seq_printf(s, "0x%08X Current SSS ", cur_pmsss);
|
|
|
|
+ seq_printf(s, lss_s0i3_enabled ? "\n" : "[BLOCKS s0i3]\n");
|
|
|
|
+
|
|
|
|
+ if (cpumask_equal(cpu_online_mask, cpumask_of(0)))
|
|
|
|
+ seq_printf(s, "cpu0 is only cpu online\n");
|
|
|
|
+ else
|
|
|
|
+ seq_printf(s, "cpu0 is NOT only cpu online [BLOCKS S0i3]\n");
|
|
|
|
+
|
|
|
|
+ seq_printf(s, "GFX: %s\n", graphics_is_off ? "" : "[BLOCKS s0i3]");
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ for_each_pci_dev(pdev) {
|
|
|
|
+ int pos;
|
|
|
|
+ u16 pmcsr;
|
|
|
|
+ struct mrst_device *mrst_dev;
|
|
|
|
+ int i;
|
|
|
|
+
|
|
|
|
+ mrst_dev = pci_id_2_mrst_dev(pdev->device);
|
|
|
|
+
|
|
|
|
+ seq_printf(s, "%s %04x/%04X %-16.16s ",
|
|
|
|
+ dev_name(&pdev->dev),
|
|
|
|
+ pdev->vendor, pdev->device,
|
|
|
|
+ dev_driver_string(&pdev->dev));
|
|
|
|
+
|
|
|
|
+ if (unlikely (!mrst_dev)) {
|
|
|
|
+ seq_printf(s, " UNKNOWN\n");
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (mrst_dev->lss)
|
|
|
|
+ seq_printf(s, "LSS %2d %-4s ", mrst_dev->lss,
|
|
|
|
+ d0ix_name(((cur_pmsss >>
|
|
|
|
+ (mrst_dev->lss * 2)) & 0x3)));
|
|
|
|
+ else
|
|
|
|
+ seq_printf(s, " ");
|
|
|
|
+
|
|
|
|
+ /* PCI PM config space setting */
|
|
|
|
+ pos = pci_find_capability(pdev, PCI_CAP_ID_PM);
|
|
|
|
+ if (pos != 0) {
|
|
|
|
+ pci_read_config_word(pdev, pos + PCI_PM_CTRL, &pmcsr);
|
|
|
|
+ seq_printf(s, "PCI-%-4s",
|
|
|
|
+ pci_power_name(pmcsr & PCI_PM_CTRL_STATE_MASK));
|
|
|
|
+ } else {
|
|
|
|
+ seq_printf(s, " ");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ seq_printf(s, " %s ", pci_power_name(mrst_dev->latest_request));
|
|
|
|
+ for (i = 0; i <= PCI_D3cold; ++i)
|
|
|
|
+ seq_printf(s, "%d ", mrst_dev->pci_state_counts[i]);
|
|
|
|
+
|
|
|
|
+ if (mrst_dev->lss) {
|
|
|
|
+ unsigned int lssmask;
|
|
|
|
+
|
|
|
|
+ lssmask = SSMSK(D0i3, mrst_dev->lss);
|
|
|
|
+
|
|
|
|
+ if ((lssmask & S0I3_SSS_TARGET) &&
|
|
|
|
+ ((lssmask & cur_pmsss) !=
|
|
|
|
+ (lssmask & S0I3_SSS_TARGET)))
|
|
|
|
+ seq_printf(s , "[BLOCKS s0i3]");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ seq_printf(s, "\n");
|
|
|
|
+ }
|
|
|
|
+ seq_printf(s, "Wake Counters:\n");
|
|
|
|
+ for (lss = 0; lss < MRST_NUM_LSS; ++lss)
|
|
|
|
+ seq_printf(s, "LSS%d %d\n", lss, wake_counters[lss]);
|
|
|
|
+
|
|
|
|
+ seq_printf(s, "Interrupt Counters:\n");
|
|
|
|
+ seq_printf(s,
|
|
|
|
+ "INT_SPURIOUS \t%8u\n" "INT_CMD_DONE \t%8u\n"
|
|
|
|
+ "INT_CMD_ERR \t%8u\n" "INT_WAKE_RX \t%8u\n"
|
|
|
|
+ "INT_SS_ERROR \t%8u\n" "INT_S0IX_MISS\t%8u\n"
|
|
|
|
+ "INT_NO_ACKC6 \t%8u\n" "INT_INVALID \t%8u\n",
|
|
|
|
+ pmu_irq_stats[INT_SPURIOUS], pmu_irq_stats[INT_CMD_DONE],
|
|
|
|
+ pmu_irq_stats[INT_CMD_ERR], pmu_irq_stats[INT_WAKE_RX],
|
|
|
|
+ pmu_irq_stats[INT_SS_ERROR], pmu_irq_stats[INT_S0IX_MISS],
|
|
|
|
+ pmu_irq_stats[INT_NO_ACKC6], pmu_irq_stats[INT_INVALID]);
|
|
|
|
+
|
|
|
|
+ seq_printf(s, "mrst_pmu_wait_ready_calls %8d\n",
|
|
|
|
+ pmu_wait_ready_calls);
|
|
|
|
+ seq_printf(s, "mrst_pmu_wait_ready_udelays %8d\n",
|
|
|
|
+ pmu_wait_ready_udelays);
|
|
|
|
+ seq_printf(s, "mrst_pmu_wait_ready_udelays_max %8d\n",
|
|
|
|
+ pmu_wait_ready_udelays_max);
|
|
|
|
+ seq_printf(s, "mrst_pmu_wait_done_calls %8d\n",
|
|
|
|
+ pmu_wait_done_calls);
|
|
|
|
+ seq_printf(s, "mrst_pmu_wait_done_udelays %8d\n",
|
|
|
|
+ pmu_wait_done_udelays);
|
|
|
|
+ seq_printf(s, "mrst_pmu_wait_done_udelays_max %8d\n",
|
|
|
|
+ pmu_wait_done_udelays_max);
|
|
|
|
+ seq_printf(s, "mrst_pmu_set_power_state_entry %8d\n",
|
|
|
|
+ pmu_set_power_state_entry);
|
|
|
|
+ seq_printf(s, "mrst_pmu_set_power_state_send_cmd %8d\n",
|
|
|
|
+ pmu_set_power_state_send_cmd);
|
|
|
|
+ seq_printf(s, "SCU busy: %d\n", pmu_read_busy_status());
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int debug_mrst_pmu_open(struct inode *inode, struct file *file)
|
|
|
|
+{
|
|
|
|
+ return single_open(file, debug_mrst_pmu_show, NULL);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct file_operations devices_state_operations = {
|
|
|
|
+ .open = debug_mrst_pmu_open,
|
|
|
|
+ .read = seq_read,
|
|
|
|
+ .llseek = seq_lseek,
|
|
|
|
+ .release = single_release,
|
|
|
|
+};
|
|
|
|
+#endif /* DEBUG_FS */
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Validate SCU PCI shim PCI vendor capability byte
|
|
|
|
+ * against LSS hard-coded in mrst_devs[] above.
|
|
|
|
+ * DEBUG only.
|
|
|
|
+ */
|
|
|
|
+static void pmu_scu_firmware_debug(void)
|
|
|
|
+{
|
|
|
|
+ struct pci_dev *pdev = NULL;
|
|
|
|
+
|
|
|
|
+ for_each_pci_dev(pdev) {
|
|
|
|
+ struct mrst_device *mrst_dev;
|
|
|
|
+ u8 pci_config_lss;
|
|
|
|
+ int pos;
|
|
|
|
+
|
|
|
|
+ mrst_dev = pci_id_2_mrst_dev(pdev->device);
|
|
|
|
+ if (unlikely(!mrst_dev)) {
|
|
|
|
+ printk(KERN_ERR FW_BUG "pmu: Unknown "
|
|
|
|
+ "PCI device 0x%04X\n", pdev->device);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (mrst_dev->lss == 0)
|
|
|
|
+ continue; /* no LSS in our table */
|
|
|
|
+
|
|
|
|
+ pos = pci_find_capability(pdev, PCI_CAP_ID_VNDR);
|
|
|
|
+ if (!pos != 0) {
|
|
|
|
+ printk(KERN_ERR FW_BUG "pmu: 0x%04X "
|
|
|
|
+ "missing PCI Vendor Capability\n",
|
|
|
|
+ pdev->device);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ pci_read_config_byte(pdev, pos + 4, &pci_config_lss);
|
|
|
|
+ if (!(pci_config_lss & PCI_VENDOR_CAP_LOG_SS_MASK)) {
|
|
|
|
+ printk(KERN_ERR FW_BUG "pmu: 0x%04X "
|
|
|
|
+ "invalid PCI Vendor Capability 0x%x "
|
|
|
|
+ " expected LSS 0x%X\n",
|
|
|
|
+ pdev->device, pci_config_lss, mrst_dev->lss);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ pci_config_lss &= PCI_VENDOR_CAP_LOG_ID_MASK;
|
|
|
|
+
|
|
|
|
+ if (mrst_dev->lss == pci_config_lss)
|
|
|
|
+ continue;
|
|
|
|
+
|
|
|
|
+ printk(KERN_ERR FW_BUG "pmu: 0x%04X LSS = %d, expected %d\n",
|
|
|
|
+ pdev->device, pci_config_lss, mrst_dev->lss);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * pmu_probe
|
|
|
|
+ */
|
|
|
|
+static int __devinit pmu_probe(struct pci_dev *pdev,
|
|
|
|
+ const struct pci_device_id *pci_id)
|
|
|
|
+{
|
|
|
|
+ int ret;
|
|
|
|
+ struct mrst_pmu_reg *pmu;
|
|
|
|
+
|
|
|
|
+ /* Init the device */
|
|
|
|
+ ret = pci_enable_device(pdev);
|
|
|
|
+ if (ret) {
|
|
|
|
+ dev_err(&pdev->dev, "Unable to Enable PCI device\n");
|
|
|
|
+ return ret;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ret = pci_request_regions(pdev, MRST_PMU_DRV_NAME);
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ dev_err(&pdev->dev, "Cannot obtain PCI resources, aborting\n");
|
|
|
|
+ goto out_err1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Map the memory of PMU reg base */
|
|
|
|
+ pmu = pci_iomap(pdev, 0, 0);
|
|
|
|
+ if (!pmu) {
|
|
|
|
+ dev_err(&pdev->dev, "Unable to map the PMU address space\n");
|
|
|
|
+ ret = -ENOMEM;
|
|
|
|
+ goto out_err2;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+#ifdef CONFIG_DEBUG_FS
|
|
|
|
+ /* /sys/kernel/debug/mrst_pmu */
|
|
|
|
+ (void) debugfs_create_file("mrst_pmu", S_IFREG | S_IRUGO,
|
|
|
|
+ NULL, NULL, &devices_state_operations);
|
|
|
|
+#endif
|
|
|
|
+ pmu_reg = pmu; /* success */
|
|
|
|
+
|
|
|
|
+ if (request_irq(pdev->irq, pmu_irq, 0, MRST_PMU_DRV_NAME, NULL)) {
|
|
|
|
+ dev_err(&pdev->dev, "Registering isr has failed\n");
|
|
|
|
+ ret = -1;
|
|
|
|
+ goto out_err3;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ pmu_scu_firmware_debug();
|
|
|
|
+
|
|
|
|
+ pmu_write_wkc(S0I3_WAKE_SOURCES); /* Enable S0i3 wakeup sources */
|
|
|
|
+
|
|
|
|
+ pmu_wait_ready();
|
|
|
|
+
|
|
|
|
+ pmu_write_ssc(D0I1_ACG_SSS_TARGET); /* Enable Auto-Clock_Gating */
|
|
|
|
+ pmu_write_cmd(0x201);
|
|
|
|
+
|
|
|
|
+ spin_lock_init(&mrst_pmu_power_state_lock);
|
|
|
|
+
|
|
|
|
+ /* Enable the hardware interrupt */
|
|
|
|
+ pmu_irq_enable();
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+out_err3:
|
|
|
|
+ free_irq(pdev->irq, NULL);
|
|
|
|
+ pci_iounmap(pdev, pmu_reg);
|
|
|
|
+ pmu_reg = NULL;
|
|
|
|
+out_err2:
|
|
|
|
+ pci_release_region(pdev, 0);
|
|
|
|
+out_err1:
|
|
|
|
+ pci_disable_device(pdev);
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void __devexit pmu_remove(struct pci_dev *pdev)
|
|
|
|
+{
|
|
|
|
+ dev_err(&pdev->dev, "Mid PM pmu_remove called\n");
|
|
|
|
+
|
|
|
|
+ /* Freeing up the irq */
|
|
|
|
+ free_irq(pdev->irq, NULL);
|
|
|
|
+
|
|
|
|
+ pci_iounmap(pdev, pmu_reg);
|
|
|
|
+ pmu_reg = NULL;
|
|
|
|
+
|
|
|
|
+ /* disable the current PCI device */
|
|
|
|
+ pci_release_region(pdev, 0);
|
|
|
|
+ pci_disable_device(pdev);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static DEFINE_PCI_DEVICE_TABLE(pmu_pci_ids) = {
|
|
|
|
+ { PCI_VDEVICE(INTEL, PCI_DEV_ID_MRST_PMU), 0 },
|
|
|
|
+ { }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+MODULE_DEVICE_TABLE(pci, pmu_pci_ids);
|
|
|
|
+
|
|
|
|
+static struct pci_driver driver = {
|
|
|
|
+ .name = MRST_PMU_DRV_NAME,
|
|
|
|
+ .id_table = pmu_pci_ids,
|
|
|
|
+ .probe = pmu_probe,
|
|
|
|
+ .remove = __devexit_p(pmu_remove),
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * pmu_pci_register - register the PMU driver as PCI device
|
|
|
|
+ */
|
|
|
|
+static int __init pmu_pci_register(void)
|
|
|
|
+{
|
|
|
|
+ return pci_register_driver(&driver);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* Register and probe via fs_initcall() to preceed device_initcall() */
|
|
|
|
+fs_initcall(pmu_pci_register);
|
|
|
|
+
|
|
|
|
+static void __exit mid_pci_cleanup(void)
|
|
|
|
+{
|
|
|
|
+ pci_unregister_driver(&driver);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int ia_major;
|
|
|
|
+static int ia_minor;
|
|
|
|
+
|
|
|
|
+static int pmu_sfi_parse_oem(struct sfi_table_header *table)
|
|
|
|
+{
|
|
|
|
+ struct sfi_table_simple *sb;
|
|
|
|
+
|
|
|
|
+ sb = (struct sfi_table_simple *)table;
|
|
|
|
+ ia_major = (sb->pentry[1] >> 0) & 0xFFFF;
|
|
|
|
+ ia_minor = (sb->pentry[1] >> 16) & 0xFFFF;
|
|
|
|
+ printk(KERN_INFO "mrst_pmu: IA FW version v%x.%x\n",
|
|
|
|
+ ia_major, ia_minor);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int __init scu_fw_check(void)
|
|
|
|
+{
|
|
|
|
+ int ret;
|
|
|
|
+ u32 fw_version;
|
|
|
|
+
|
|
|
|
+ if (!pmu_reg)
|
|
|
|
+ return 0; /* this driver didn't probe-out */
|
|
|
|
+
|
|
|
|
+ sfi_table_parse("OEMB", NULL, NULL, pmu_sfi_parse_oem);
|
|
|
|
+
|
|
|
|
+ if (ia_major < 0x6005 || ia_minor < 0x1525) {
|
|
|
|
+ WARN(1, "mrst_pmu: IA FW version too old\n");
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ret = intel_scu_ipc_command(IPCMSG_FW_REVISION, 0, NULL, 0,
|
|
|
|
+ &fw_version, 1);
|
|
|
|
+
|
|
|
|
+ if (ret) {
|
|
|
|
+ WARN(1, "mrst_pmu: IPC FW version? %d\n", ret);
|
|
|
|
+ } else {
|
|
|
|
+ int scu_major = (fw_version >> 8) & 0xFF;
|
|
|
|
+ int scu_minor = (fw_version >> 0) & 0xFF;
|
|
|
|
+
|
|
|
|
+ printk(KERN_INFO "mrst_pmu: firmware v%x\n", fw_version);
|
|
|
|
+
|
|
|
|
+ if ((scu_major >= 0xC0) && (scu_minor >= 0x49)) {
|
|
|
|
+ printk(KERN_INFO "mrst_pmu: enabling S0i3\n");
|
|
|
|
+ mrst_pmu_s0i3_enable = true;
|
|
|
|
+ } else {
|
|
|
|
+ WARN(1, "mrst_pmu: S0i3 disabled, old firmware %X.%X",
|
|
|
|
+ scu_major, scu_minor);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+late_initcall(scu_fw_check);
|
|
|
|
+module_exit(mid_pci_cleanup);
|