summaryrefslogtreecommitdiff
path: root/drivers/hid/intel-ish-hid
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid/intel-ish-hid')
-rw-r--r--drivers/hid/intel-ish-hid/ipc/ipc.c101
-rw-r--r--drivers/hid/intel-ish-hid/ipc/pci-ish.c31
-rw-r--r--drivers/hid/intel-ish-hid/ishtp-hid-client.c15
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/bus.c18
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/client.c6
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/hbm.c4
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h3
7 files changed, 123 insertions, 55 deletions
diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c
index 3ddaa2cd39d5..abf9c9a31c39 100644
--- a/drivers/hid/intel-ish-hid/ipc/ipc.c
+++ b/drivers/hid/intel-ish-hid/ipc/ipc.c
@@ -481,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
@@ -496,13 +510,9 @@ out:
*/
static int ish_fw_reset_handler(struct ishtp_device *dev)
{
- uint32_t reset_id;
unsigned long flags;
int ret;
- /* Read reset ID */
- reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF;
-
/* Clear IPC output queue */
spin_lock_irqsave(&dev->wr_processing_spinlock, flags);
list_splice_init(&dev->wr_processing_list, &dev->wr_free_list);
@@ -521,15 +531,6 @@ static int ish_fw_reset_handler(struct ishtp_device *dev)
/* Send clock sync at once after reset */
ishtp_dev->prev_sync = 0;
- /*
- * 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));
-
/* Wait for ISH FW'es ILUP and ISHTP_READY */
ret = timed_wait_for_timeout(dev, WAIT_FOR_FW_RDY,
TIME_SLICE_FOR_FW_RDY_MS,
@@ -563,8 +564,6 @@ static void fw_reset_work_fn(struct work_struct *work)
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 */
if (!work_pending(work))
@@ -625,15 +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;
- }
- 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;
}
}
@@ -730,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;
}
/**
@@ -794,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
@@ -833,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;
@@ -857,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);
}
/**
@@ -933,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
@@ -953,6 +963,10 @@ 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);
@@ -982,6 +996,7 @@ 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");
diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
index 9d150ce234f2..1612e8cb23f0 100644
--- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c
+++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
@@ -147,6 +147,12 @@ static inline bool ish_should_enter_d0i3(struct pci_dev *pdev)
static inline bool ish_should_leave_d0i3(struct pci_dev *pdev)
{
+ struct ishtp_device *dev = pci_get_drvdata(pdev);
+ u32 fwsts = dev->ops->get_fw_status(dev);
+
+ if (dev->suspend_flag || !IPC_IS_ISH_ILUP(fwsts))
+ return false;
+
return !pm_resume_via_firmware() || pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV;
}
@@ -277,10 +283,8 @@ 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);
- if (ish_should_leave_d0i3(pdev) && !dev->suspend_flag
- && IPC_IS_ISH_ILUP(fwsts)) {
+ if (ish_should_leave_d0i3(pdev)) {
if (device_may_wakeup(&pdev->dev))
disable_irq_wake(pdev->irq);
@@ -384,12 +388,29 @@ static int __maybe_unused ish_resume(struct device *device)
ish_resume_device = device;
dev->resume_flag = 1;
- schedule_work(&resume_work);
+ /* If ISH resume from D3, reset ishtp clients before return */
+ if (!ish_should_leave_d0i3(pdev))
+ ishtp_reset_handler(dev);
+
+ queue_work(dev->unbound_wq, &resume_work);
return 0;
}
-static SIMPLE_DEV_PM_OPS(ish_pm_ops, ish_suspend, ish_resume);
+static int __maybe_unused ish_freeze(struct device *device)
+{
+ struct pci_dev *pdev = to_pci_dev(device);
+
+ return pci_save_state(pdev);
+}
+
+static const struct dev_pm_ops __maybe_unused ish_pm_ops = {
+ .suspend = pm_sleep_ptr(ish_suspend),
+ .resume = pm_sleep_ptr(ish_resume),
+ .freeze = pm_sleep_ptr(ish_freeze),
+ .restore = pm_sleep_ptr(ish_resume),
+ .poweroff = pm_sleep_ptr(ish_suspend),
+};
static ssize_t base_version_show(struct device *cdev,
struct device_attribute *attr, char *buf)
diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c
index d8c3c54a8c0f..f37b3bc2bb7d 100644
--- a/drivers/hid/intel-ish-hid/ishtp-hid-client.c
+++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c
@@ -757,8 +757,15 @@ static void hid_ishtp_cl_resume_handler(struct work_struct *work)
struct ishtp_cl *hid_ishtp_cl = client_data->hid_ishtp_cl;
if (ishtp_wait_resume(ishtp_get_ishtp_device(hid_ishtp_cl))) {
- client_data->suspended = false;
- wake_up_interruptible(&client_data->ishtp_resume_wait);
+ /*
+ * Clear the suspended flag only when the connection is established.
+ * If the connection is not established, the suspended flag will be cleared after
+ * the connection is made.
+ */
+ if (ishtp_get_connection_state(hid_ishtp_cl) == ISHTP_CL_CONNECTED) {
+ client_data->suspended = false;
+ wake_up_interruptible(&client_data->ishtp_resume_wait);
+ }
} else {
hid_ishtp_trace(client_data, "hid client: wait for resume timed out");
dev_err(cl_data_to_dev(client_data), "wait for resume timed out");
@@ -860,7 +867,7 @@ static int hid_ishtp_cl_reset(struct ishtp_cl_device *cl_device)
hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__,
hid_ishtp_cl);
- schedule_work(&client_data->work);
+ queue_work(ishtp_get_workqueue(cl_device), &client_data->work);
return 0;
}
@@ -902,7 +909,7 @@ static int hid_ishtp_cl_resume(struct device *device)
hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__,
hid_ishtp_cl);
- schedule_work(&client_data->resume_work);
+ queue_work(ishtp_get_workqueue(cl_device), &client_data->resume_work);
return 0;
}
diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c
index 93a0432e7058..c6ce37244e49 100644
--- a/drivers/hid/intel-ish-hid/ishtp/bus.c
+++ b/drivers/hid/intel-ish-hid/ishtp/bus.c
@@ -541,7 +541,7 @@ void ishtp_cl_bus_rx_event(struct ishtp_cl_device *device)
return;
if (device->event_cb)
- schedule_work(&device->event_work);
+ queue_work(device->ishtp_dev->unbound_wq, &device->event_work);
}
/**
@@ -877,6 +877,22 @@ struct device *ishtp_get_pci_device(struct ishtp_cl_device *device)
EXPORT_SYMBOL(ishtp_get_pci_device);
/**
+ * ishtp_get_workqueue - Retrieve the workqueue associated with an ISHTP device
+ * @cl_device: Pointer to the ISHTP client device structure
+ *
+ * Returns the workqueue_struct pointer (unbound_wq) associated with the given
+ * ISHTP client device. This workqueue is typically used for scheduling work
+ * related to the device.
+ *
+ * Return: Pointer to struct workqueue_struct.
+ */
+struct workqueue_struct *ishtp_get_workqueue(struct ishtp_cl_device *cl_device)
+{
+ return cl_device->ishtp_dev->unbound_wq;
+}
+EXPORT_SYMBOL(ishtp_get_workqueue);
+
+/**
* ishtp_trace_callback() - Return trace callback
* @cl_device: ISH-TP client device instance
*
diff --git a/drivers/hid/intel-ish-hid/ishtp/client.c b/drivers/hid/intel-ish-hid/ishtp/client.c
index 21a2c0773cc2..40f510b1c072 100644
--- a/drivers/hid/intel-ish-hid/ishtp/client.c
+++ b/drivers/hid/intel-ish-hid/ishtp/client.c
@@ -1261,6 +1261,12 @@ void ishtp_set_connection_state(struct ishtp_cl *cl, int state)
}
EXPORT_SYMBOL(ishtp_set_connection_state);
+int ishtp_get_connection_state(struct ishtp_cl *cl)
+{
+ return cl->state;
+}
+EXPORT_SYMBOL(ishtp_get_connection_state);
+
void ishtp_cl_set_fw_client_id(struct ishtp_cl *cl, int fw_client_id)
{
cl->fw_client_id = fw_client_id;
diff --git a/drivers/hid/intel-ish-hid/ishtp/hbm.c b/drivers/hid/intel-ish-hid/ishtp/hbm.c
index 8ee5467127d8..97c4fcd9e3c6 100644
--- a/drivers/hid/intel-ish-hid/ishtp/hbm.c
+++ b/drivers/hid/intel-ish-hid/ishtp/hbm.c
@@ -573,7 +573,7 @@ void ishtp_hbm_dispatch(struct ishtp_device *dev,
/* Start firmware loading process if it has loader capability */
if (version_res->host_version_supported & ISHTP_SUPPORT_CAP_LOADER)
- schedule_work(&dev->work_fw_loader);
+ queue_work(dev->unbound_wq, &dev->work_fw_loader);
dev->version.major_version = HBM_MAJOR_VERSION;
dev->version.minor_version = HBM_MINOR_VERSION;
@@ -864,7 +864,7 @@ void recv_hbm(struct ishtp_device *dev, struct ishtp_msg_hdr *ishtp_hdr)
dev->rd_msg_fifo_tail = (dev->rd_msg_fifo_tail + IPC_PAYLOAD_SIZE) %
(RD_INT_FIFO_SIZE * IPC_PAYLOAD_SIZE);
spin_unlock_irqrestore(&dev->rd_msg_spinlock, flags);
- schedule_work(&dev->bh_hbm_work);
+ queue_work(dev->unbound_wq, &dev->bh_hbm_work);
eoi:
return;
}
diff --git a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
index 23db97ecf21c..4b0596eadf1c 100644
--- a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
+++ b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
@@ -175,6 +175,9 @@ struct ishtp_device {
struct hbm_version version;
int transfer_path; /* Choice of transfer path: IPC or DMA */
+ /* Alloc a dedicated unbound workqueue for ishtp device */
+ struct workqueue_struct *unbound_wq;
+
/* work structure for scheduling firmware loading tasks */
struct work_struct work_fw_loader;
/* waitq for waiting for command response from the firmware loader */