diff options
Diffstat (limited to 'drivers/hid/intel-ish-hid/ipc/ipc.c')
| -rw-r--r-- | drivers/hid/intel-ish-hid/ipc/ipc.c | 159 |
1 files changed, 95 insertions, 64 deletions
diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c index 45e0c7b1c9ec..abf9c9a31c39 100644 --- a/drivers/hid/intel-ish-hid/ipc/ipc.c +++ b/drivers/hid/intel-ish-hid/ipc/ipc.c @@ -5,6 +5,7 @@ * Copyright (c) 2014-2016, Intel Corporation. */ +#include <linux/devm-helpers.h> #include <linux/sched.h> #include <linux/spinlock.h> #include <linux/delay.h> @@ -77,7 +78,7 @@ static bool check_generated_interrupt(struct ishtp_device *dev) bool interrupt_generated = true; uint32_t pisr_val = 0; - if (dev->pdev->device == CHV_DEVICE_ID) { + if (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV) { pisr_val = ish_reg_read(dev, IPC_REG_PISR_CHV_AB); interrupt_generated = IPC_INT_FROM_ISH_TO_HOST_CHV_AB(pisr_val); @@ -116,7 +117,7 @@ static bool ish_is_input_ready(struct ishtp_device *dev) */ static void set_host_ready(struct ishtp_device *dev) { - if (dev->pdev->device == CHV_DEVICE_ID) { + if (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV) { if (dev->pdev->revision == REVISION_ID_CHT_A0 || (dev->pdev->revision & REVISION_ID_SI_MASK) == REVISION_ID_CHT_Ax_SI) @@ -480,6 +481,20 @@ out: return ret; } +static void ish_send_reset_notify_ack(struct ishtp_device *dev) +{ + /* Read reset ID */ + u32 reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF; + + /* + * Set HOST2ISH.ILUP. Apparently we need this BEFORE sending + * RESET_NOTIFY_ACK - FW will be checking for it + */ + ish_set_host_rdy(dev); + /* Send RESET_NOTIFY_ACK (with reset_id) */ + ipc_send_mng_msg(dev, MNG_RESET_NOTIFY_ACK, &reset_id, sizeof(u32)); +} + #define TIME_SLICE_FOR_FW_RDY_MS 100 #define TIME_SLICE_FOR_INPUT_RDY_MS 100 #define TIMEOUT_FOR_FW_RDY_MS 2000 @@ -495,11 +510,8 @@ out: */ static int ish_fw_reset_handler(struct ishtp_device *dev) { - uint32_t reset_id; unsigned long flags; - - /* Read reset ID */ - reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF; + int ret; /* Clear IPC output queue */ spin_lock_irqsave(&dev->wr_processing_spinlock, flags); @@ -509,26 +521,21 @@ static int ish_fw_reset_handler(struct ishtp_device *dev) /* ISHTP notification in IPC_RESET */ ishtp_reset_handler(dev); - if (!ish_is_input_ready(dev)) - timed_wait_for_timeout(dev, WAIT_FOR_INPUT_RDY, - TIME_SLICE_FOR_INPUT_RDY_MS, TIMEOUT_FOR_INPUT_RDY_MS); - + ret = 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)) + if (ret) return -EPIPE; - /* - * Set HOST2ISH.ILUP. Apparently we need this BEFORE sending - * RESET_NOTIFY_ACK - FW will be checking for it - */ - ish_set_host_rdy(dev); - /* Send RESET_NOTIFY_ACK (with reset_id) */ - ipc_send_mng_msg(dev, MNG_RESET_NOTIFY_ACK, &reset_id, - sizeof(uint32_t)); + + /* Send clock sync at once after reset */ + ishtp_dev->prev_sync = 0; /* Wait for ISH FW'es ILUP and ISHTP_READY */ - 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)) { + ret = timed_wait_for_timeout(dev, WAIT_FOR_FW_RDY, + TIME_SLICE_FOR_FW_RDY_MS, + TIMEOUT_FOR_FW_RDY_MS); + if (ret) { /* ISH FW is dead */ uint32_t ish_status; @@ -545,11 +552,11 @@ static int ish_fw_reset_handler(struct ishtp_device *dev) /** * fw_reset_work_fn() - FW reset worker function - * @unused: not used + * @work: Work item * * Call ish_fw_reset_handler to complete FW reset */ -static void fw_reset_work_fn(struct work_struct *unused) +static void fw_reset_work_fn(struct work_struct *work) { int rv; @@ -557,11 +564,10 @@ static void fw_reset_work_fn(struct work_struct *unused) if (!rv) { /* ISH is ILUP & ISHTP-ready. Restart ISHTP */ msleep_interruptible(TIMEOUT_FOR_HW_RDY_MS); - ishtp_dev->recvd_hw_ready = 1; - wake_up_interruptible(&ishtp_dev->wait_hw_ready); /* ISHTP notification in IPC_RESET sequence completion */ - ishtp_reset_compl_handler(ishtp_dev); + if (!work_pending(work)) + ishtp_reset_compl_handler(ishtp_dev); } else dev_err(ishtp_dev->devc, "[ishtp-ish]: FW reset failed (%d)\n", rv); @@ -575,15 +581,14 @@ static void fw_reset_work_fn(struct work_struct *unused) */ static void _ish_sync_fw_clock(struct ishtp_device *dev) { - static unsigned long prev_sync; - uint64_t usec; + struct ipc_time_update_msg time = {}; - if (prev_sync && jiffies - prev_sync < 20 * HZ) + if (dev->prev_sync && time_before(jiffies, dev->prev_sync + 20 * HZ)) return; - prev_sync = jiffies; - usec = ktime_to_us(ktime_get_boottime()); - ipc_send_mng_msg(dev, MNG_SYNC_FW_CLOCK, &usec, sizeof(uint64_t)); + dev->prev_sync = jiffies; + /* The fields of time would be updated while sending message */ + ipc_send_mng_msg(dev, MNG_SYNC_FW_CLOCK, &time, sizeof(time)); } /** @@ -619,16 +624,14 @@ static void recv_ipc(struct ishtp_device *dev, uint32_t doorbell_val) break; case MNG_RESET_NOTIFY: - if (!ishtp_dev) { - ishtp_dev = dev; - INIT_WORK(&fw_reset_work, fw_reset_work_fn); - } - schedule_work(&fw_reset_work); - break; + ish_send_reset_notify_ack(ishtp_dev); + fallthrough; case MNG_RESET_NOTIFY_ACK: dev->recvd_hw_ready = 1; wake_up_interruptible(&dev->wait_hw_ready); + if (!work_pending(&fw_reset_work)) + queue_work(dev->unbound_wq, &fw_reset_work); break; } } @@ -725,22 +728,28 @@ int ish_disable_dma(struct ishtp_device *dev) * 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, + * Set the dma enable bit and send a IPC RESET message to FW, * it wil wakeup FW from waiting-for-host state. + * + * Return: 0 for success else error code. */ -static void ish_wakeup(struct ishtp_device *dev) +static int ish_wakeup(struct ishtp_device *dev) { + int ret; + /* 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 + * Send IPC RESET message so that ISH FW wakes up if it was already * asleep. */ - ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT); + ret = ish_ipc_reset(dev); /* Flush writes to doorbell and REMAP2 */ ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); + + return ret; } /** @@ -789,11 +798,11 @@ static int _ish_hw_reset(struct ishtp_device *dev) pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr); /* Now we can enable ISH DMA operation and wakeup ISHFW */ - ish_wakeup(dev); - - return 0; + return ish_wakeup(dev); } +#define RECVD_HW_READY_TIMEOUT (10 * HZ) + /** * _ish_ipc_reset() - IPC reset * @dev: ishtp device pointer @@ -828,7 +837,8 @@ static int _ish_ipc_reset(struct ishtp_device *dev) } wait_event_interruptible_timeout(dev->wait_hw_ready, - dev->recvd_hw_ready, 2 * HZ); + dev->recvd_hw_ready, + RECVD_HW_READY_TIMEOUT); if (!dev->recvd_hw_ready) { dev_err(dev->devc, "Timed out waiting for HW ready\n"); rv = -ENODEV; @@ -852,21 +862,7 @@ int ish_hw_start(struct ishtp_device *dev) set_host_ready(dev); /* After that we can enable ISH DMA operation and wakeup ISHFW */ - ish_wakeup(dev); - - /* wait for FW-initiated reset flow */ - if (!dev->recvd_hw_ready) - wait_event_interruptible_timeout(dev->wait_hw_ready, - dev->recvd_hw_ready, - 10 * HZ); - - if (!dev->recvd_hw_ready) { - dev_err(dev->devc, - "[ishtp-ish]: Timed out waiting for FW-initiated reset\n"); - return -ENODEV; - } - - return 0; + return ish_wakeup(dev); } /** @@ -909,7 +905,11 @@ static uint32_t ish_ipc_get_header(struct ishtp_device *dev, int length, */ static bool _dma_no_cache_snooping(struct ishtp_device *dev) { - return dev->pdev->device == EHL_Ax_DEVICE_ID; + return (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax || + dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_TGL_LP || + dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_TGL_H || + dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_ADL_S || + dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_ADL_P); } static const struct ishtp_hw_ops ish_hw_ops = { @@ -924,6 +924,25 @@ static const struct ishtp_hw_ops ish_hw_ops = { .dma_no_cache_snooping = _dma_no_cache_snooping }; +static void ishtp_free_workqueue(void *wq) +{ + destroy_workqueue(wq); +} + +static struct workqueue_struct *devm_ishtp_alloc_workqueue(struct device *dev) +{ + struct workqueue_struct *wq; + + wq = alloc_workqueue("ishtp_unbound_%d", WQ_UNBOUND, 0, dev->id); + if (!wq) + return NULL; + + if (devm_add_action_or_reset(dev, ishtp_free_workqueue, wq)) + return NULL; + + return wq; +} + /** * ish_dev_init() -Initialize ISH devoce * @pdev: PCI device @@ -936,6 +955,7 @@ struct ishtp_device *ish_dev_init(struct pci_dev *pdev) { struct ishtp_device *dev; int i; + int ret; dev = devm_kzalloc(&pdev->dev, sizeof(struct ishtp_device) + sizeof(struct ish_hw), @@ -943,6 +963,11 @@ struct ishtp_device *ish_dev_init(struct pci_dev *pdev) if (!dev) return NULL; + dev->unbound_wq = devm_ishtp_alloc_workqueue(&pdev->dev); + if (!dev->unbound_wq) + return NULL; + + dev->devc = &pdev->dev; ishtp_device_init(dev); init_waitqueue_head(&dev->wait_hw_ready); @@ -971,8 +996,14 @@ struct ishtp_device *ish_dev_init(struct pci_dev *pdev) list_add_tail(&tx_buf->link, &dev->wr_free_list); } + ishtp_dev = dev; + ret = devm_work_autocancel(&pdev->dev, &fw_reset_work, fw_reset_work_fn); + if (ret) { + dev_err(dev->devc, "Failed to initialise FW reset work\n"); + return NULL; + } + dev->ops = &ish_hw_ops; - dev->devc = &pdev->dev; dev->mtu = IPC_PAYLOAD_SIZE - sizeof(struct ishtp_msg_hdr); return dev; } |
