xref: /OK3568_Linux_fs/kernel/drivers/hid/intel-ish-hid/ipc/pci-ish.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun  * PCI glue for ISHTP provider device (ISH) driver
4*4882a593Smuzhiyun  *
5*4882a593Smuzhiyun  * Copyright (c) 2014-2016, Intel Corporation.
6*4882a593Smuzhiyun  */
7*4882a593Smuzhiyun 
8*4882a593Smuzhiyun #include <linux/module.h>
9*4882a593Smuzhiyun #include <linux/moduleparam.h>
10*4882a593Smuzhiyun #include <linux/kernel.h>
11*4882a593Smuzhiyun #include <linux/device.h>
12*4882a593Smuzhiyun #include <linux/fs.h>
13*4882a593Smuzhiyun #include <linux/errno.h>
14*4882a593Smuzhiyun #include <linux/types.h>
15*4882a593Smuzhiyun #include <linux/pci.h>
16*4882a593Smuzhiyun #include <linux/sched.h>
17*4882a593Smuzhiyun #include <linux/suspend.h>
18*4882a593Smuzhiyun #include <linux/interrupt.h>
19*4882a593Smuzhiyun #include <linux/workqueue.h>
20*4882a593Smuzhiyun #define CREATE_TRACE_POINTS
21*4882a593Smuzhiyun #include <trace/events/intel_ish.h>
22*4882a593Smuzhiyun #include "ishtp-dev.h"
23*4882a593Smuzhiyun #include "hw-ish.h"
24*4882a593Smuzhiyun 
25*4882a593Smuzhiyun static const struct pci_device_id ish_pci_tbl[] = {
26*4882a593Smuzhiyun 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CHV_DEVICE_ID)},
27*4882a593Smuzhiyun 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Ax_DEVICE_ID)},
28*4882a593Smuzhiyun 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Bx_DEVICE_ID)},
29*4882a593Smuzhiyun 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, APL_Ax_DEVICE_ID)},
30*4882a593Smuzhiyun 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_Ax_DEVICE_ID)},
31*4882a593Smuzhiyun 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_Ax_DEVICE_ID)},
32*4882a593Smuzhiyun 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, GLK_Ax_DEVICE_ID)},
33*4882a593Smuzhiyun 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_H_DEVICE_ID)},
34*4882a593Smuzhiyun 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ICL_MOBILE_DEVICE_ID)},
35*4882a593Smuzhiyun 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_H_DEVICE_ID)},
36*4882a593Smuzhiyun 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CML_LP_DEVICE_ID)},
37*4882a593Smuzhiyun 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CMP_H_DEVICE_ID)},
38*4882a593Smuzhiyun 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, EHL_Ax_DEVICE_ID)},
39*4882a593Smuzhiyun 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, TGL_LP_DEVICE_ID)},
40*4882a593Smuzhiyun 	{0, }
41*4882a593Smuzhiyun };
42*4882a593Smuzhiyun MODULE_DEVICE_TABLE(pci, ish_pci_tbl);
43*4882a593Smuzhiyun 
44*4882a593Smuzhiyun /**
45*4882a593Smuzhiyun  * ish_event_tracer() - Callback function to dump trace messages
46*4882a593Smuzhiyun  * @dev:	ishtp device
47*4882a593Smuzhiyun  * @format:	printf style format
48*4882a593Smuzhiyun  *
49*4882a593Smuzhiyun  * Callback to direct log messages to Linux trace buffers
50*4882a593Smuzhiyun  */
51*4882a593Smuzhiyun static __printf(2, 3)
ish_event_tracer(struct ishtp_device * dev,const char * format,...)52*4882a593Smuzhiyun void ish_event_tracer(struct ishtp_device *dev, const char *format, ...)
53*4882a593Smuzhiyun {
54*4882a593Smuzhiyun 	if (trace_ishtp_dump_enabled()) {
55*4882a593Smuzhiyun 		va_list args;
56*4882a593Smuzhiyun 		char tmp_buf[100];
57*4882a593Smuzhiyun 
58*4882a593Smuzhiyun 		va_start(args, format);
59*4882a593Smuzhiyun 		vsnprintf(tmp_buf, sizeof(tmp_buf), format, args);
60*4882a593Smuzhiyun 		va_end(args);
61*4882a593Smuzhiyun 
62*4882a593Smuzhiyun 		trace_ishtp_dump(tmp_buf);
63*4882a593Smuzhiyun 	}
64*4882a593Smuzhiyun }
65*4882a593Smuzhiyun 
66*4882a593Smuzhiyun /**
67*4882a593Smuzhiyun  * ish_init() - Init function
68*4882a593Smuzhiyun  * @dev:	ishtp device
69*4882a593Smuzhiyun  *
70*4882a593Smuzhiyun  * This function initialize wait queues for suspend/resume and call
71*4882a593Smuzhiyun  * calls hadware initialization function. This will initiate
72*4882a593Smuzhiyun  * startup sequence
73*4882a593Smuzhiyun  *
74*4882a593Smuzhiyun  * Return: 0 for success or error code for failure
75*4882a593Smuzhiyun  */
ish_init(struct ishtp_device * dev)76*4882a593Smuzhiyun static int ish_init(struct ishtp_device *dev)
77*4882a593Smuzhiyun {
78*4882a593Smuzhiyun 	int ret;
79*4882a593Smuzhiyun 
80*4882a593Smuzhiyun 	/* Set the state of ISH HW to start */
81*4882a593Smuzhiyun 	ret = ish_hw_start(dev);
82*4882a593Smuzhiyun 	if (ret) {
83*4882a593Smuzhiyun 		dev_err(dev->devc, "ISH: hw start failed.\n");
84*4882a593Smuzhiyun 		return ret;
85*4882a593Smuzhiyun 	}
86*4882a593Smuzhiyun 
87*4882a593Smuzhiyun 	/* Start the inter process communication to ISH processor */
88*4882a593Smuzhiyun 	ret = ishtp_start(dev);
89*4882a593Smuzhiyun 	if (ret) {
90*4882a593Smuzhiyun 		dev_err(dev->devc, "ISHTP: Protocol init failed.\n");
91*4882a593Smuzhiyun 		return ret;
92*4882a593Smuzhiyun 	}
93*4882a593Smuzhiyun 
94*4882a593Smuzhiyun 	return 0;
95*4882a593Smuzhiyun }
96*4882a593Smuzhiyun 
97*4882a593Smuzhiyun static const struct pci_device_id ish_invalid_pci_ids[] = {
98*4882a593Smuzhiyun 	/* Mehlow platform special pci ids */
99*4882a593Smuzhiyun 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xA309)},
100*4882a593Smuzhiyun 	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xA30A)},
101*4882a593Smuzhiyun 	{}
102*4882a593Smuzhiyun };
103*4882a593Smuzhiyun 
ish_should_enter_d0i3(struct pci_dev * pdev)104*4882a593Smuzhiyun static inline bool ish_should_enter_d0i3(struct pci_dev *pdev)
105*4882a593Smuzhiyun {
106*4882a593Smuzhiyun 	return !pm_suspend_via_firmware() || pdev->device == CHV_DEVICE_ID;
107*4882a593Smuzhiyun }
108*4882a593Smuzhiyun 
ish_should_leave_d0i3(struct pci_dev * pdev)109*4882a593Smuzhiyun static inline bool ish_should_leave_d0i3(struct pci_dev *pdev)
110*4882a593Smuzhiyun {
111*4882a593Smuzhiyun 	return !pm_resume_via_firmware() || pdev->device == CHV_DEVICE_ID;
112*4882a593Smuzhiyun }
113*4882a593Smuzhiyun 
114*4882a593Smuzhiyun /**
115*4882a593Smuzhiyun  * ish_probe() - PCI driver probe callback
116*4882a593Smuzhiyun  * @pdev:	pci device
117*4882a593Smuzhiyun  * @ent:	pci device id
118*4882a593Smuzhiyun  *
119*4882a593Smuzhiyun  * Initialize PCI function, setup interrupt and call for ISH initialization
120*4882a593Smuzhiyun  *
121*4882a593Smuzhiyun  * Return: 0 for success or error code for failure
122*4882a593Smuzhiyun  */
ish_probe(struct pci_dev * pdev,const struct pci_device_id * ent)123*4882a593Smuzhiyun static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
124*4882a593Smuzhiyun {
125*4882a593Smuzhiyun 	int ret;
126*4882a593Smuzhiyun 	struct ish_hw *hw;
127*4882a593Smuzhiyun 	unsigned long irq_flag = 0;
128*4882a593Smuzhiyun 	struct ishtp_device *ishtp;
129*4882a593Smuzhiyun 	struct device *dev = &pdev->dev;
130*4882a593Smuzhiyun 
131*4882a593Smuzhiyun 	/* Check for invalid platforms for ISH support */
132*4882a593Smuzhiyun 	if (pci_dev_present(ish_invalid_pci_ids))
133*4882a593Smuzhiyun 		return -ENODEV;
134*4882a593Smuzhiyun 
135*4882a593Smuzhiyun 	/* enable pci dev */
136*4882a593Smuzhiyun 	ret = pcim_enable_device(pdev);
137*4882a593Smuzhiyun 	if (ret) {
138*4882a593Smuzhiyun 		dev_err(dev, "ISH: Failed to enable PCI device\n");
139*4882a593Smuzhiyun 		return ret;
140*4882a593Smuzhiyun 	}
141*4882a593Smuzhiyun 
142*4882a593Smuzhiyun 	/* set PCI host mastering */
143*4882a593Smuzhiyun 	pci_set_master(pdev);
144*4882a593Smuzhiyun 
145*4882a593Smuzhiyun 	/* pci request regions for ISH driver */
146*4882a593Smuzhiyun 	ret = pcim_iomap_regions(pdev, 1 << 0, KBUILD_MODNAME);
147*4882a593Smuzhiyun 	if (ret) {
148*4882a593Smuzhiyun 		dev_err(dev, "ISH: Failed to get PCI regions\n");
149*4882a593Smuzhiyun 		return ret;
150*4882a593Smuzhiyun 	}
151*4882a593Smuzhiyun 
152*4882a593Smuzhiyun 	/* allocates and initializes the ISH dev structure */
153*4882a593Smuzhiyun 	ishtp = ish_dev_init(pdev);
154*4882a593Smuzhiyun 	if (!ishtp) {
155*4882a593Smuzhiyun 		ret = -ENOMEM;
156*4882a593Smuzhiyun 		return ret;
157*4882a593Smuzhiyun 	}
158*4882a593Smuzhiyun 	hw = to_ish_hw(ishtp);
159*4882a593Smuzhiyun 	ishtp->print_log = ish_event_tracer;
160*4882a593Smuzhiyun 
161*4882a593Smuzhiyun 	/* mapping IO device memory */
162*4882a593Smuzhiyun 	hw->mem_addr = pcim_iomap_table(pdev)[0];
163*4882a593Smuzhiyun 	ishtp->pdev = pdev;
164*4882a593Smuzhiyun 
165*4882a593Smuzhiyun 	/* request and enable interrupt */
166*4882a593Smuzhiyun 	ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
167*4882a593Smuzhiyun 	if (!pdev->msi_enabled && !pdev->msix_enabled)
168*4882a593Smuzhiyun 		irq_flag = IRQF_SHARED;
169*4882a593Smuzhiyun 
170*4882a593Smuzhiyun 	ret = devm_request_irq(dev, pdev->irq, ish_irq_handler,
171*4882a593Smuzhiyun 			       irq_flag, KBUILD_MODNAME, ishtp);
172*4882a593Smuzhiyun 	if (ret) {
173*4882a593Smuzhiyun 		dev_err(dev, "ISH: request IRQ %d failed\n", pdev->irq);
174*4882a593Smuzhiyun 		return ret;
175*4882a593Smuzhiyun 	}
176*4882a593Smuzhiyun 
177*4882a593Smuzhiyun 	dev_set_drvdata(ishtp->devc, ishtp);
178*4882a593Smuzhiyun 
179*4882a593Smuzhiyun 	init_waitqueue_head(&ishtp->suspend_wait);
180*4882a593Smuzhiyun 	init_waitqueue_head(&ishtp->resume_wait);
181*4882a593Smuzhiyun 
182*4882a593Smuzhiyun 	ret = ish_init(ishtp);
183*4882a593Smuzhiyun 	if (ret)
184*4882a593Smuzhiyun 		return ret;
185*4882a593Smuzhiyun 
186*4882a593Smuzhiyun 	return 0;
187*4882a593Smuzhiyun }
188*4882a593Smuzhiyun 
189*4882a593Smuzhiyun /**
190*4882a593Smuzhiyun  * ish_remove() - PCI driver remove callback
191*4882a593Smuzhiyun  * @pdev:	pci device
192*4882a593Smuzhiyun  *
193*4882a593Smuzhiyun  * This function does cleanup of ISH on pci remove callback
194*4882a593Smuzhiyun  */
ish_remove(struct pci_dev * pdev)195*4882a593Smuzhiyun static void ish_remove(struct pci_dev *pdev)
196*4882a593Smuzhiyun {
197*4882a593Smuzhiyun 	struct ishtp_device *ishtp_dev = pci_get_drvdata(pdev);
198*4882a593Smuzhiyun 
199*4882a593Smuzhiyun 	ishtp_bus_remove_all_clients(ishtp_dev, false);
200*4882a593Smuzhiyun 	ish_device_disable(ishtp_dev);
201*4882a593Smuzhiyun }
202*4882a593Smuzhiyun 
203*4882a593Smuzhiyun static struct device __maybe_unused *ish_resume_device;
204*4882a593Smuzhiyun 
205*4882a593Smuzhiyun /* 50ms to get resume response */
206*4882a593Smuzhiyun #define WAIT_FOR_RESUME_ACK_MS		50
207*4882a593Smuzhiyun 
208*4882a593Smuzhiyun /**
209*4882a593Smuzhiyun  * ish_resume_handler() - Work function to complete resume
210*4882a593Smuzhiyun  * @work:	work struct
211*4882a593Smuzhiyun  *
212*4882a593Smuzhiyun  * The resume work function to complete resume function asynchronously.
213*4882a593Smuzhiyun  * There are two resume paths, one where ISH is not powered off,
214*4882a593Smuzhiyun  * in that case a simple resume message is enough, others we need
215*4882a593Smuzhiyun  * a reset sequence.
216*4882a593Smuzhiyun  */
ish_resume_handler(struct work_struct * work)217*4882a593Smuzhiyun static void __maybe_unused ish_resume_handler(struct work_struct *work)
218*4882a593Smuzhiyun {
219*4882a593Smuzhiyun 	struct pci_dev *pdev = to_pci_dev(ish_resume_device);
220*4882a593Smuzhiyun 	struct ishtp_device *dev = pci_get_drvdata(pdev);
221*4882a593Smuzhiyun 	int ret;
222*4882a593Smuzhiyun 
223*4882a593Smuzhiyun 	if (ish_should_leave_d0i3(pdev) && !dev->suspend_flag) {
224*4882a593Smuzhiyun 		disable_irq_wake(pdev->irq);
225*4882a593Smuzhiyun 
226*4882a593Smuzhiyun 		ishtp_send_resume(dev);
227*4882a593Smuzhiyun 
228*4882a593Smuzhiyun 		/* Waiting to get resume response */
229*4882a593Smuzhiyun 		if (dev->resume_flag)
230*4882a593Smuzhiyun 			ret = wait_event_interruptible_timeout(dev->resume_wait,
231*4882a593Smuzhiyun 				!dev->resume_flag,
232*4882a593Smuzhiyun 				msecs_to_jiffies(WAIT_FOR_RESUME_ACK_MS));
233*4882a593Smuzhiyun 
234*4882a593Smuzhiyun 		/*
235*4882a593Smuzhiyun 		 * If the flag is not cleared, something is wrong with ISH FW.
236*4882a593Smuzhiyun 		 * So on resume, need to go through init sequence again.
237*4882a593Smuzhiyun 		 */
238*4882a593Smuzhiyun 		if (dev->resume_flag)
239*4882a593Smuzhiyun 			ish_init(dev);
240*4882a593Smuzhiyun 	} else {
241*4882a593Smuzhiyun 		/*
242*4882a593Smuzhiyun 		 * Resume from the D3, full reboot of ISH processor will happen,
243*4882a593Smuzhiyun 		 * so need to go through init sequence again.
244*4882a593Smuzhiyun 		 */
245*4882a593Smuzhiyun 		ish_init(dev);
246*4882a593Smuzhiyun 	}
247*4882a593Smuzhiyun }
248*4882a593Smuzhiyun 
249*4882a593Smuzhiyun /**
250*4882a593Smuzhiyun  * ish_suspend() - ISH suspend callback
251*4882a593Smuzhiyun  * @device:	device pointer
252*4882a593Smuzhiyun  *
253*4882a593Smuzhiyun  * ISH suspend callback
254*4882a593Smuzhiyun  *
255*4882a593Smuzhiyun  * Return: 0 to the pm core
256*4882a593Smuzhiyun  */
ish_suspend(struct device * device)257*4882a593Smuzhiyun static int __maybe_unused ish_suspend(struct device *device)
258*4882a593Smuzhiyun {
259*4882a593Smuzhiyun 	struct pci_dev *pdev = to_pci_dev(device);
260*4882a593Smuzhiyun 	struct ishtp_device *dev = pci_get_drvdata(pdev);
261*4882a593Smuzhiyun 
262*4882a593Smuzhiyun 	if (ish_should_enter_d0i3(pdev)) {
263*4882a593Smuzhiyun 		/*
264*4882a593Smuzhiyun 		 * If previous suspend hasn't been asnwered then ISH is likely
265*4882a593Smuzhiyun 		 * dead, don't attempt nested notification
266*4882a593Smuzhiyun 		 */
267*4882a593Smuzhiyun 		if (dev->suspend_flag)
268*4882a593Smuzhiyun 			return	0;
269*4882a593Smuzhiyun 
270*4882a593Smuzhiyun 		dev->resume_flag = 0;
271*4882a593Smuzhiyun 		dev->suspend_flag = 1;
272*4882a593Smuzhiyun 		ishtp_send_suspend(dev);
273*4882a593Smuzhiyun 
274*4882a593Smuzhiyun 		/* 25 ms should be enough for live ISH to flush all IPC buf */
275*4882a593Smuzhiyun 		if (dev->suspend_flag)
276*4882a593Smuzhiyun 			wait_event_interruptible_timeout(dev->suspend_wait,
277*4882a593Smuzhiyun 					!dev->suspend_flag,
278*4882a593Smuzhiyun 					msecs_to_jiffies(25));
279*4882a593Smuzhiyun 
280*4882a593Smuzhiyun 		if (dev->suspend_flag) {
281*4882a593Smuzhiyun 			/*
282*4882a593Smuzhiyun 			 * It looks like FW halt, clear the DMA bit, and put
283*4882a593Smuzhiyun 			 * ISH into D3, and FW would reset on resume.
284*4882a593Smuzhiyun 			 */
285*4882a593Smuzhiyun 			ish_disable_dma(dev);
286*4882a593Smuzhiyun 		} else {
287*4882a593Smuzhiyun 			/*
288*4882a593Smuzhiyun 			 * Save state so PCI core will keep the device at D0,
289*4882a593Smuzhiyun 			 * the ISH would enter D0i3
290*4882a593Smuzhiyun 			 */
291*4882a593Smuzhiyun 			pci_save_state(pdev);
292*4882a593Smuzhiyun 
293*4882a593Smuzhiyun 			enable_irq_wake(pdev->irq);
294*4882a593Smuzhiyun 		}
295*4882a593Smuzhiyun 	} else {
296*4882a593Smuzhiyun 		/*
297*4882a593Smuzhiyun 		 * Clear the DMA bit before putting ISH into D3,
298*4882a593Smuzhiyun 		 * or ISH FW would reset automatically.
299*4882a593Smuzhiyun 		 */
300*4882a593Smuzhiyun 		ish_disable_dma(dev);
301*4882a593Smuzhiyun 	}
302*4882a593Smuzhiyun 
303*4882a593Smuzhiyun 	return 0;
304*4882a593Smuzhiyun }
305*4882a593Smuzhiyun 
306*4882a593Smuzhiyun static __maybe_unused DECLARE_WORK(resume_work, ish_resume_handler);
307*4882a593Smuzhiyun /**
308*4882a593Smuzhiyun  * ish_resume() - ISH resume callback
309*4882a593Smuzhiyun  * @device:	device pointer
310*4882a593Smuzhiyun  *
311*4882a593Smuzhiyun  * ISH resume callback
312*4882a593Smuzhiyun  *
313*4882a593Smuzhiyun  * Return: 0 to the pm core
314*4882a593Smuzhiyun  */
ish_resume(struct device * device)315*4882a593Smuzhiyun static int __maybe_unused ish_resume(struct device *device)
316*4882a593Smuzhiyun {
317*4882a593Smuzhiyun 	struct pci_dev *pdev = to_pci_dev(device);
318*4882a593Smuzhiyun 	struct ishtp_device *dev = pci_get_drvdata(pdev);
319*4882a593Smuzhiyun 
320*4882a593Smuzhiyun 	ish_resume_device = device;
321*4882a593Smuzhiyun 	dev->resume_flag = 1;
322*4882a593Smuzhiyun 
323*4882a593Smuzhiyun 	schedule_work(&resume_work);
324*4882a593Smuzhiyun 
325*4882a593Smuzhiyun 	return 0;
326*4882a593Smuzhiyun }
327*4882a593Smuzhiyun 
328*4882a593Smuzhiyun static SIMPLE_DEV_PM_OPS(ish_pm_ops, ish_suspend, ish_resume);
329*4882a593Smuzhiyun 
330*4882a593Smuzhiyun static struct pci_driver ish_driver = {
331*4882a593Smuzhiyun 	.name = KBUILD_MODNAME,
332*4882a593Smuzhiyun 	.id_table = ish_pci_tbl,
333*4882a593Smuzhiyun 	.probe = ish_probe,
334*4882a593Smuzhiyun 	.remove = ish_remove,
335*4882a593Smuzhiyun 	.driver.pm = &ish_pm_ops,
336*4882a593Smuzhiyun };
337*4882a593Smuzhiyun 
338*4882a593Smuzhiyun module_pci_driver(ish_driver);
339*4882a593Smuzhiyun 
340*4882a593Smuzhiyun /* Original author */
341*4882a593Smuzhiyun MODULE_AUTHOR("Daniel Drubin <daniel.drubin@intel.com>");
342*4882a593Smuzhiyun /* Adoption to upstream Linux kernel */
343*4882a593Smuzhiyun MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
344*4882a593Smuzhiyun 
345*4882a593Smuzhiyun MODULE_DESCRIPTION("Intel(R) Integrated Sensor Hub PCI Device Driver");
346*4882a593Smuzhiyun MODULE_LICENSE("GPL");
347