Commit | Line | Data |
---|---|---|
795536ac TW |
1 | /* |
2 | * | |
3 | * Intel Management Engine Interface (Intel MEI) Linux driver | |
4 | * Copyright (c) 2013-2014, Intel Corporation. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms and conditions of the GNU General Public License, | |
8 | * version 2, as published by the Free Software Foundation. | |
9 | * | |
10 | * This program is distributed in the hope it will be useful, but WITHOUT | |
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
13 | * more details. | |
14 | * | |
15 | */ | |
16 | ||
17 | #include <linux/module.h> | |
18 | #include <linux/kernel.h> | |
19 | #include <linux/device.h> | |
20 | #include <linux/fs.h> | |
21 | #include <linux/errno.h> | |
22 | #include <linux/types.h> | |
23 | #include <linux/pci.h> | |
24 | #include <linux/init.h> | |
25 | #include <linux/sched.h> | |
26 | #include <linux/uuid.h> | |
27 | #include <linux/jiffies.h> | |
28 | #include <linux/interrupt.h> | |
29 | #include <linux/workqueue.h> | |
989561de | 30 | #include <linux/pm_domain.h> |
cfe5ab85 | 31 | #include <linux/pm_runtime.h> |
795536ac TW |
32 | |
33 | #include <linux/mei.h> | |
34 | ||
35 | ||
36 | #include "mei_dev.h" | |
37 | #include "hw-txe.h" | |
38 | ||
a05f8f86 | 39 | static const struct pci_device_id mei_txe_pci_tbl[] = { |
4ad96db6 | 40 | {PCI_VDEVICE(INTEL, 0x0F18)}, /* Baytrail */ |
e88281ed | 41 | {PCI_VDEVICE(INTEL, 0x2298)}, /* Cherrytrail */ |
4ad96db6 | 42 | |
795536ac TW |
43 | {0, } |
44 | }; | |
45 | MODULE_DEVICE_TABLE(pci, mei_txe_pci_tbl); | |
46 | ||
bbd6d050 | 47 | #ifdef CONFIG_PM |
d2d56fae AU |
48 | static inline void mei_txe_set_pm_domain(struct mei_device *dev); |
49 | static inline void mei_txe_unset_pm_domain(struct mei_device *dev); | |
50 | #else | |
51 | static inline void mei_txe_set_pm_domain(struct mei_device *dev) {} | |
52 | static inline void mei_txe_unset_pm_domain(struct mei_device *dev) {} | |
bbd6d050 | 53 | #endif /* CONFIG_PM */ |
795536ac TW |
54 | |
55 | static void mei_txe_pci_iounmap(struct pci_dev *pdev, struct mei_txe_hw *hw) | |
56 | { | |
57 | int i; | |
92db1555 | 58 | |
795536ac TW |
59 | for (i = SEC_BAR; i < NUM_OF_MEM_BARS; i++) { |
60 | if (hw->mem_addr[i]) { | |
61 | pci_iounmap(pdev, hw->mem_addr[i]); | |
62 | hw->mem_addr[i] = NULL; | |
63 | } | |
64 | } | |
65 | } | |
66 | /** | |
3908be6f | 67 | * mei_txe_probe - Device Initialization Routine |
795536ac TW |
68 | * |
69 | * @pdev: PCI device structure | |
70 | * @ent: entry in mei_txe_pci_tbl | |
71 | * | |
a8605ea2 | 72 | * Return: 0 on success, <0 on failure. |
795536ac TW |
73 | */ |
74 | static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) | |
75 | { | |
76 | struct mei_device *dev; | |
77 | struct mei_txe_hw *hw; | |
78 | int err; | |
79 | int i; | |
80 | ||
81 | /* enable pci dev */ | |
82 | err = pci_enable_device(pdev); | |
83 | if (err) { | |
84 | dev_err(&pdev->dev, "failed to enable pci device.\n"); | |
85 | goto end; | |
86 | } | |
87 | /* set PCI host mastering */ | |
88 | pci_set_master(pdev); | |
89 | /* pci request regions for mei driver */ | |
90 | err = pci_request_regions(pdev, KBUILD_MODNAME); | |
91 | if (err) { | |
92 | dev_err(&pdev->dev, "failed to get pci regions.\n"); | |
93 | goto disable_device; | |
94 | } | |
95 | ||
96 | err = pci_set_dma_mask(pdev, DMA_BIT_MASK(36)); | |
97 | if (err) { | |
98 | err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); | |
99 | if (err) { | |
100 | dev_err(&pdev->dev, "No suitable DMA available.\n"); | |
101 | goto release_regions; | |
102 | } | |
103 | } | |
104 | ||
105 | /* allocates and initializes the mei dev structure */ | |
4ad96db6 | 106 | dev = mei_txe_dev_init(pdev); |
795536ac TW |
107 | if (!dev) { |
108 | err = -ENOMEM; | |
109 | goto release_regions; | |
110 | } | |
111 | hw = to_txe_hw(dev); | |
112 | ||
113 | /* mapping IO device memory */ | |
114 | for (i = SEC_BAR; i < NUM_OF_MEM_BARS; i++) { | |
115 | hw->mem_addr[i] = pci_iomap(pdev, i, 0); | |
116 | if (!hw->mem_addr[i]) { | |
117 | dev_err(&pdev->dev, "mapping I/O device memory failure.\n"); | |
118 | err = -ENOMEM; | |
119 | goto free_device; | |
120 | } | |
121 | } | |
122 | ||
123 | ||
124 | pci_enable_msi(pdev); | |
125 | ||
126 | /* clear spurious interrupts */ | |
127 | mei_clear_interrupts(dev); | |
128 | ||
129 | /* request and enable interrupt */ | |
130 | if (pci_dev_msi_enabled(pdev)) | |
131 | err = request_threaded_irq(pdev->irq, | |
132 | NULL, | |
133 | mei_txe_irq_thread_handler, | |
134 | IRQF_ONESHOT, KBUILD_MODNAME, dev); | |
135 | else | |
136 | err = request_threaded_irq(pdev->irq, | |
137 | mei_txe_irq_quick_handler, | |
138 | mei_txe_irq_thread_handler, | |
139 | IRQF_SHARED, KBUILD_MODNAME, dev); | |
140 | if (err) { | |
141 | dev_err(&pdev->dev, "mei: request_threaded_irq failure. irq = %d\n", | |
142 | pdev->irq); | |
143 | goto free_device; | |
144 | } | |
145 | ||
146 | if (mei_start(dev)) { | |
147 | dev_err(&pdev->dev, "init hw failure.\n"); | |
148 | err = -ENODEV; | |
149 | goto release_irq; | |
150 | } | |
151 | ||
cfe5ab85 AU |
152 | pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_TXI_RPM_TIMEOUT); |
153 | pm_runtime_use_autosuspend(&pdev->dev); | |
154 | ||
f3d8e878 | 155 | err = mei_register(dev, &pdev->dev); |
795536ac | 156 | if (err) |
1f7e489a | 157 | goto stop; |
795536ac TW |
158 | |
159 | pci_set_drvdata(pdev, dev); | |
160 | ||
d2d56fae AU |
161 | /* |
162 | * For not wake-able HW runtime pm framework | |
163 | * can't be used on pci device level. | |
164 | * Use domain runtime pm callbacks instead. | |
165 | */ | |
166 | if (!pci_dev_run_wake(pdev)) | |
167 | mei_txe_set_pm_domain(dev); | |
168 | ||
cfe5ab85 AU |
169 | pm_runtime_put_noidle(&pdev->dev); |
170 | ||
795536ac TW |
171 | return 0; |
172 | ||
1f7e489a AU |
173 | stop: |
174 | mei_stop(dev); | |
795536ac TW |
175 | release_irq: |
176 | ||
177 | mei_cancel_work(dev); | |
178 | ||
179 | /* disable interrupts */ | |
180 | mei_disable_interrupts(dev); | |
181 | ||
182 | free_irq(pdev->irq, dev); | |
183 | pci_disable_msi(pdev); | |
184 | ||
185 | free_device: | |
186 | mei_txe_pci_iounmap(pdev, hw); | |
187 | ||
188 | kfree(dev); | |
189 | release_regions: | |
190 | pci_release_regions(pdev); | |
191 | disable_device: | |
192 | pci_disable_device(pdev); | |
193 | end: | |
194 | dev_err(&pdev->dev, "initialization failed.\n"); | |
195 | return err; | |
196 | } | |
197 | ||
198 | /** | |
3908be6f | 199 | * mei_txe_remove - Device Removal Routine |
795536ac TW |
200 | * |
201 | * @pdev: PCI device structure | |
202 | * | |
203 | * mei_remove is called by the PCI subsystem to alert the driver | |
204 | * that it should release a PCI device. | |
205 | */ | |
206 | static void mei_txe_remove(struct pci_dev *pdev) | |
207 | { | |
208 | struct mei_device *dev; | |
209 | struct mei_txe_hw *hw; | |
210 | ||
211 | dev = pci_get_drvdata(pdev); | |
212 | if (!dev) { | |
213 | dev_err(&pdev->dev, "mei: dev =NULL\n"); | |
214 | return; | |
215 | } | |
216 | ||
cfe5ab85 AU |
217 | pm_runtime_get_noresume(&pdev->dev); |
218 | ||
795536ac TW |
219 | hw = to_txe_hw(dev); |
220 | ||
221 | mei_stop(dev); | |
222 | ||
d2d56fae AU |
223 | if (!pci_dev_run_wake(pdev)) |
224 | mei_txe_unset_pm_domain(dev); | |
225 | ||
795536ac TW |
226 | /* disable interrupts */ |
227 | mei_disable_interrupts(dev); | |
228 | free_irq(pdev->irq, dev); | |
229 | pci_disable_msi(pdev); | |
230 | ||
231 | pci_set_drvdata(pdev, NULL); | |
232 | ||
233 | mei_txe_pci_iounmap(pdev, hw); | |
234 | ||
235 | mei_deregister(dev); | |
236 | ||
237 | kfree(dev); | |
238 | ||
239 | pci_release_regions(pdev); | |
240 | pci_disable_device(pdev); | |
241 | } | |
242 | ||
243 | ||
e0270add | 244 | #ifdef CONFIG_PM_SLEEP |
795536ac TW |
245 | static int mei_txe_pci_suspend(struct device *device) |
246 | { | |
247 | struct pci_dev *pdev = to_pci_dev(device); | |
248 | struct mei_device *dev = pci_get_drvdata(pdev); | |
249 | ||
250 | if (!dev) | |
251 | return -ENODEV; | |
252 | ||
253 | dev_dbg(&pdev->dev, "suspend\n"); | |
254 | ||
255 | mei_stop(dev); | |
256 | ||
257 | mei_disable_interrupts(dev); | |
258 | ||
259 | free_irq(pdev->irq, dev); | |
260 | pci_disable_msi(pdev); | |
261 | ||
262 | return 0; | |
263 | } | |
264 | ||
265 | static int mei_txe_pci_resume(struct device *device) | |
266 | { | |
267 | struct pci_dev *pdev = to_pci_dev(device); | |
268 | struct mei_device *dev; | |
269 | int err; | |
270 | ||
271 | dev = pci_get_drvdata(pdev); | |
272 | if (!dev) | |
273 | return -ENODEV; | |
274 | ||
275 | pci_enable_msi(pdev); | |
276 | ||
277 | mei_clear_interrupts(dev); | |
278 | ||
279 | /* request and enable interrupt */ | |
280 | if (pci_dev_msi_enabled(pdev)) | |
281 | err = request_threaded_irq(pdev->irq, | |
282 | NULL, | |
283 | mei_txe_irq_thread_handler, | |
284 | IRQF_ONESHOT, KBUILD_MODNAME, dev); | |
285 | else | |
286 | err = request_threaded_irq(pdev->irq, | |
287 | mei_txe_irq_quick_handler, | |
288 | mei_txe_irq_thread_handler, | |
289 | IRQF_SHARED, KBUILD_MODNAME, dev); | |
290 | if (err) { | |
291 | dev_err(&pdev->dev, "request_threaded_irq failed: irq = %d.\n", | |
292 | pdev->irq); | |
293 | return err; | |
294 | } | |
295 | ||
296 | err = mei_restart(dev); | |
297 | ||
298 | return err; | |
299 | } | |
cfe5ab85 AU |
300 | #endif /* CONFIG_PM_SLEEP */ |
301 | ||
bbd6d050 | 302 | #ifdef CONFIG_PM |
cfe5ab85 AU |
303 | static int mei_txe_pm_runtime_idle(struct device *device) |
304 | { | |
305 | struct pci_dev *pdev = to_pci_dev(device); | |
306 | struct mei_device *dev; | |
307 | ||
308 | dev_dbg(&pdev->dev, "rpm: txe: runtime_idle\n"); | |
309 | ||
310 | dev = pci_get_drvdata(pdev); | |
311 | if (!dev) | |
312 | return -ENODEV; | |
313 | if (mei_write_is_idle(dev)) | |
d5d83f8a | 314 | pm_runtime_autosuspend(device); |
cfe5ab85 AU |
315 | |
316 | return -EBUSY; | |
317 | } | |
318 | static int mei_txe_pm_runtime_suspend(struct device *device) | |
319 | { | |
320 | struct pci_dev *pdev = to_pci_dev(device); | |
321 | struct mei_device *dev; | |
322 | int ret; | |
323 | ||
324 | dev_dbg(&pdev->dev, "rpm: txe: runtime suspend\n"); | |
325 | ||
326 | dev = pci_get_drvdata(pdev); | |
327 | if (!dev) | |
328 | return -ENODEV; | |
795536ac | 329 | |
cfe5ab85 AU |
330 | mutex_lock(&dev->device_lock); |
331 | ||
332 | if (mei_write_is_idle(dev)) | |
333 | ret = mei_txe_aliveness_set_sync(dev, 0); | |
334 | else | |
335 | ret = -EAGAIN; | |
336 | ||
337 | /* | |
338 | * If everything is okay we're about to enter PCI low | |
339 | * power state (D3) therefor we need to disable the | |
340 | * interrupts towards host. | |
341 | * However if device is not wakeable we do not enter | |
342 | * D-low state and we need to keep the interrupt kicking | |
343 | */ | |
900f4450 | 344 | if (!ret && pci_dev_run_wake(pdev)) |
cfe5ab85 AU |
345 | mei_disable_interrupts(dev); |
346 | ||
347 | dev_dbg(&pdev->dev, "rpm: txe: runtime suspend ret=%d\n", ret); | |
348 | ||
349 | mutex_unlock(&dev->device_lock); | |
77537ad2 AU |
350 | |
351 | if (ret && ret != -EAGAIN) | |
352 | schedule_work(&dev->reset_work); | |
353 | ||
cfe5ab85 AU |
354 | return ret; |
355 | } | |
356 | ||
357 | static int mei_txe_pm_runtime_resume(struct device *device) | |
358 | { | |
359 | struct pci_dev *pdev = to_pci_dev(device); | |
360 | struct mei_device *dev; | |
361 | int ret; | |
362 | ||
363 | dev_dbg(&pdev->dev, "rpm: txe: runtime resume\n"); | |
364 | ||
365 | dev = pci_get_drvdata(pdev); | |
366 | if (!dev) | |
367 | return -ENODEV; | |
368 | ||
369 | mutex_lock(&dev->device_lock); | |
370 | ||
371 | mei_enable_interrupts(dev); | |
372 | ||
373 | ret = mei_txe_aliveness_set_sync(dev, 1); | |
374 | ||
375 | mutex_unlock(&dev->device_lock); | |
376 | ||
377 | dev_dbg(&pdev->dev, "rpm: txe: runtime resume ret = %d\n", ret); | |
378 | ||
77537ad2 AU |
379 | if (ret) |
380 | schedule_work(&dev->reset_work); | |
381 | ||
cfe5ab85 AU |
382 | return ret; |
383 | } | |
d2d56fae AU |
384 | |
385 | /** | |
7efceb55 | 386 | * mei_txe_set_pm_domain - fill and set pm domain structure for device |
d2d56fae AU |
387 | * |
388 | * @dev: mei_device | |
389 | */ | |
390 | static inline void mei_txe_set_pm_domain(struct mei_device *dev) | |
391 | { | |
d08b8fc0 | 392 | struct pci_dev *pdev = to_pci_dev(dev->dev); |
d2d56fae AU |
393 | |
394 | if (pdev->dev.bus && pdev->dev.bus->pm) { | |
395 | dev->pg_domain.ops = *pdev->dev.bus->pm; | |
396 | ||
397 | dev->pg_domain.ops.runtime_suspend = mei_txe_pm_runtime_suspend; | |
398 | dev->pg_domain.ops.runtime_resume = mei_txe_pm_runtime_resume; | |
399 | dev->pg_domain.ops.runtime_idle = mei_txe_pm_runtime_idle; | |
400 | ||
989561de | 401 | dev_pm_domain_set(&pdev->dev, &dev->pg_domain); |
d2d56fae AU |
402 | } |
403 | } | |
404 | ||
405 | /** | |
7efceb55 | 406 | * mei_txe_unset_pm_domain - clean pm domain structure for device |
d2d56fae AU |
407 | * |
408 | * @dev: mei_device | |
409 | */ | |
410 | static inline void mei_txe_unset_pm_domain(struct mei_device *dev) | |
411 | { | |
412 | /* stop using pm callbacks if any */ | |
989561de | 413 | dev_pm_domain_set(dev->dev, NULL); |
d2d56fae | 414 | } |
cfe5ab85 | 415 | |
cfe5ab85 AU |
416 | static const struct dev_pm_ops mei_txe_pm_ops = { |
417 | SET_SYSTEM_SLEEP_PM_OPS(mei_txe_pci_suspend, | |
418 | mei_txe_pci_resume) | |
419 | SET_RUNTIME_PM_OPS( | |
420 | mei_txe_pm_runtime_suspend, | |
421 | mei_txe_pm_runtime_resume, | |
422 | mei_txe_pm_runtime_idle) | |
423 | }; | |
795536ac TW |
424 | |
425 | #define MEI_TXE_PM_OPS (&mei_txe_pm_ops) | |
426 | #else | |
427 | #define MEI_TXE_PM_OPS NULL | |
cfe5ab85 AU |
428 | #endif /* CONFIG_PM */ |
429 | ||
795536ac TW |
430 | /* |
431 | * PCI driver structure | |
432 | */ | |
433 | static struct pci_driver mei_txe_driver = { | |
434 | .name = KBUILD_MODNAME, | |
435 | .id_table = mei_txe_pci_tbl, | |
436 | .probe = mei_txe_probe, | |
437 | .remove = mei_txe_remove, | |
438 | .shutdown = mei_txe_remove, | |
439 | .driver.pm = MEI_TXE_PM_OPS, | |
440 | }; | |
441 | ||
442 | module_pci_driver(mei_txe_driver); | |
443 | ||
444 | MODULE_AUTHOR("Intel Corporation"); | |
445 | MODULE_DESCRIPTION("Intel(R) Trusted Execution Environment Interface"); | |
446 | MODULE_LICENSE("GPL v2"); |