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