From 2e23a70edabe933284f690dff49497fb6b82b0e5 Mon Sep 17 00:00:00 2001 From: Zhang Lixu Date: Wed, 16 Dec 2020 14:36:39 +0800 Subject: HID: intel-ish-hid: ipc: finish power flow for EHL OOB The EHL (Elkhart Lake) based platforms provide a OOB (Out of band) service, which allows wakup device when the system is in S5 (Soft-Off state). This OOB service can be enabled/disabled from BIOS settings. When enabled, the ISH device gets PME wake capability. To enable PME wakeup, driver also needs to enable ACPI GPE bit. Once wakeup, BIOS will clear the wakeup bit to identify wakeup is successful. So driver need to re-enable it in resume function to keep the next wakeup capability. Since this feature is only present on EHL, we use EHL PCI device id to enable this feature. Co-developed-by: Najumon Ba Signed-off-by: Najumon Ba Signed-off-by: Even Xu Signed-off-by: Zhang Lixu Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ipc/pci-ish.c | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c index c6d48a8648b7..c9c5488e44cb 100644 --- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c +++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c @@ -5,6 +5,7 @@ * Copyright (c) 2014-2016, Intel Corporation. */ +#include #include #include #include @@ -111,6 +112,42 @@ static inline bool ish_should_leave_d0i3(struct pci_dev *pdev) return !pm_resume_via_firmware() || pdev->device == CHV_DEVICE_ID; } +static int enable_gpe(struct device *dev) +{ +#ifdef CONFIG_ACPI + acpi_status acpi_sts; + struct acpi_device *adev; + struct acpi_device_wakeup *wakeup; + + adev = ACPI_COMPANION(dev); + if (!adev) { + dev_err(dev, "get acpi handle failed\n"); + return -ENODEV; + } + wakeup = &adev->wakeup; + + acpi_sts = acpi_enable_gpe(wakeup->gpe_device, wakeup->gpe_number); + if (ACPI_FAILURE(acpi_sts)) { + dev_err(dev, "enable ose_gpe failed\n"); + return -EIO; + } + + return 0; +#else + return -ENODEV; +#endif +} + +static void enable_pme_wake(struct pci_dev *pdev) +{ + if ((pci_pme_capable(pdev, PCI_D0) || + pci_pme_capable(pdev, PCI_D3hot) || + pci_pme_capable(pdev, PCI_D3cold)) && !enable_gpe(&pdev->dev)) { + pci_pme_active(pdev, true); + dev_dbg(&pdev->dev, "ish ipc driver pme wake enabled\n"); + } +} + /** * ish_probe() - PCI driver probe callback * @pdev: pci device @@ -179,6 +216,10 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent) init_waitqueue_head(&ishtp->suspend_wait); init_waitqueue_head(&ishtp->resume_wait); + /* Enable PME for EHL */ + if (pdev->device == EHL_Ax_DEVICE_ID) + enable_pme_wake(pdev); + ret = ish_init(ishtp); if (ret) return ret; @@ -317,6 +358,13 @@ static int __maybe_unused ish_resume(struct device *device) struct pci_dev *pdev = to_pci_dev(device); struct ishtp_device *dev = pci_get_drvdata(pdev); + /* add this to finish power flow for EHL */ + if (dev->pdev->device == EHL_Ax_DEVICE_ID) { + pci_set_power_state(pdev, PCI_D0); + enable_pme_wake(pdev); + dev_dbg(dev->devc, "set power state to D0 for ehl\n"); + } + ish_resume_device = device; dev->resume_flag = 1; -- cgit From 2f4ec1548b4e816b25c1486df30b1a2920c62cbc Mon Sep 17 00:00:00 2001 From: Zhang Lixu Date: Wed, 16 Dec 2020 14:36:40 +0800 Subject: HID: intel-ish-hid: ipc: Address EHL Sx resume issues When OOB is disabled, FW will be power gated when system is in S3/S4/S5 which is the same behavior with legacy ISH FW. When OOB is enabled, FW will always power on which is totally different comparing to legacy ISH FW. So NO_D3 flag is not enough to check FW's status after resume. Here we can use IPC FW status register to check host link status. If it is false, it means FW get reset after power gated, need go through the whole initialization flow; If it is true, it means FW is alive, just set host ready bit to let fw know host is up. Co-developed-by: Wei Jiang Signed-off-by: Wei Jiang Signed-off-by: Even Xu Signed-off-by: Zhang Lixu Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ipc/hw-ish.h | 1 + drivers/hid/intel-ish-hid/ipc/ipc.c | 27 +++++++++++++++++++++++++++ drivers/hid/intel-ish-hid/ipc/pci-ish.c | 6 +++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/drivers/hid/intel-ish-hid/ipc/hw-ish.h b/drivers/hid/intel-ish-hid/ipc/hw-ish.h index 1fb294ca463e..111ad259ba74 100644 --- a/drivers/hid/intel-ish-hid/ipc/hw-ish.h +++ b/drivers/hid/intel-ish-hid/ipc/hw-ish.h @@ -81,5 +81,6 @@ struct ishtp_device *ish_dev_init(struct pci_dev *pdev); int ish_hw_start(struct ishtp_device *dev); void ish_device_disable(struct ishtp_device *dev); int ish_disable_dma(struct ishtp_device *dev); +void ish_set_host_ready(struct ishtp_device *dev); #endif /* _ISHTP_HW_ISH_H_ */ diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c index a45ac7fa417b..47bbeb8b492b 100644 --- a/drivers/hid/intel-ish-hid/ipc/ipc.c +++ b/drivers/hid/intel-ish-hid/ipc/ipc.c @@ -193,6 +193,33 @@ static void ish_clr_host_rdy(struct ishtp_device *dev) ish_reg_write(dev, IPC_REG_HOST_COMM, host_status); } +static bool ish_chk_host_rdy(struct ishtp_device *dev) +{ + uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM); + + return (host_status & IPC_HOSTCOMM_READY_BIT); +} + +/** + * ish_set_host_ready() - reconfig ipc host registers + * @dev: ishtp device pointer + * + * Set host to ready state + * This API is called in some case: + * fw is still on, but ipc is powered down. + * such as OOB case. + * + * Return: 0 for success else error fault code + */ +void ish_set_host_ready(struct ishtp_device *dev) +{ + if (ish_chk_host_rdy(dev)) + return; + + ish_set_host_rdy(dev); + set_host_ready(dev); +} + /** * _ishtp_read_hdr() - Read message header * @dev: ISHTP device pointer diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c index c9c5488e44cb..8cb40696984a 100644 --- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c +++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c @@ -259,11 +259,15 @@ static void __maybe_unused ish_resume_handler(struct work_struct *work) { struct pci_dev *pdev = to_pci_dev(ish_resume_device); struct ishtp_device *dev = pci_get_drvdata(pdev); + uint32_t fwsts = dev->ops->get_fw_status(dev); int ret; - if (ish_should_leave_d0i3(pdev) && !dev->suspend_flag) { + if (ish_should_leave_d0i3(pdev) && !dev->suspend_flag + && IPC_IS_ISH_ILUP(fwsts)) { disable_irq_wake(pdev->irq); + ish_set_host_ready(dev); + ishtp_send_resume(dev); /* Waiting to get resume response */ -- cgit