]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * PCI glue for ISHTP provider device (ISH) driver | |
3 | * | |
4 | * Copyright (c) 2014-2016, 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 | #include <linux/module.h> | |
17 | #include <linux/moduleparam.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/sched.h> | |
25 | #include <linux/interrupt.h> | |
26 | #include <linux/workqueue.h> | |
27 | #include <linux/miscdevice.h> | |
28 | #define CREATE_TRACE_POINTS | |
29 | #include <trace/events/intel_ish.h> | |
30 | #include "ishtp-dev.h" | |
31 | #include "hw-ish.h" | |
32 | ||
33 | static const struct pci_device_id ish_pci_tbl[] = { | |
34 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, CHV_DEVICE_ID)}, | |
35 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Ax_DEVICE_ID)}, | |
36 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Bx_DEVICE_ID)}, | |
37 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, APL_Ax_DEVICE_ID)}, | |
38 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_Ax_DEVICE_ID)}, | |
39 | {0, } | |
40 | }; | |
41 | MODULE_DEVICE_TABLE(pci, ish_pci_tbl); | |
42 | ||
43 | /** | |
44 | * ish_event_tracer() - Callback function to dump trace messages | |
45 | * @dev: ishtp device | |
46 | * @format: printf style format | |
47 | * | |
48 | * Callback to direct log messages to Linux trace buffers | |
49 | */ | |
50 | static void ish_event_tracer(struct ishtp_device *dev, char *format, ...) | |
51 | { | |
52 | if (trace_ishtp_dump_enabled()) { | |
53 | va_list args; | |
54 | char tmp_buf[100]; | |
55 | ||
56 | va_start(args, format); | |
57 | vsnprintf(tmp_buf, sizeof(tmp_buf), format, args); | |
58 | va_end(args); | |
59 | ||
60 | trace_ishtp_dump(tmp_buf); | |
61 | } | |
62 | } | |
63 | ||
64 | /** | |
65 | * ish_init() - Init function | |
66 | * @dev: ishtp device | |
67 | * | |
68 | * This function initialize wait queues for suspend/resume and call | |
69 | * calls hadware initialization function. This will initiate | |
70 | * startup sequence | |
71 | * | |
72 | * Return: 0 for success or error code for failure | |
73 | */ | |
74 | static int ish_init(struct ishtp_device *dev) | |
75 | { | |
76 | int ret; | |
77 | ||
78 | /* Set the state of ISH HW to start */ | |
79 | ret = ish_hw_start(dev); | |
80 | if (ret) { | |
81 | dev_err(dev->devc, "ISH: hw start failed.\n"); | |
82 | return ret; | |
83 | } | |
84 | ||
85 | /* Start the inter process communication to ISH processor */ | |
86 | ret = ishtp_start(dev); | |
87 | if (ret) { | |
88 | dev_err(dev->devc, "ISHTP: Protocol init failed.\n"); | |
89 | return ret; | |
90 | } | |
91 | ||
92 | return 0; | |
93 | } | |
94 | ||
95 | /** | |
96 | * ish_probe() - PCI driver probe callback | |
97 | * @pdev: pci device | |
98 | * @ent: pci device id | |
99 | * | |
100 | * Initialize PCI function, setup interrupt and call for ISH initialization | |
101 | * | |
102 | * Return: 0 for success or error code for failure | |
103 | */ | |
104 | static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent) | |
105 | { | |
106 | struct ishtp_device *dev; | |
107 | struct ish_hw *hw; | |
108 | int ret; | |
109 | ||
110 | /* enable pci dev */ | |
111 | ret = pci_enable_device(pdev); | |
112 | if (ret) { | |
113 | dev_err(&pdev->dev, "ISH: Failed to enable PCI device\n"); | |
114 | return ret; | |
115 | } | |
116 | ||
117 | /* set PCI host mastering */ | |
118 | pci_set_master(pdev); | |
119 | ||
120 | /* pci request regions for ISH driver */ | |
121 | ret = pci_request_regions(pdev, KBUILD_MODNAME); | |
122 | if (ret) { | |
123 | dev_err(&pdev->dev, "ISH: Failed to get PCI regions\n"); | |
124 | goto disable_device; | |
125 | } | |
126 | ||
127 | /* allocates and initializes the ISH dev structure */ | |
128 | dev = ish_dev_init(pdev); | |
129 | if (!dev) { | |
130 | ret = -ENOMEM; | |
131 | goto release_regions; | |
132 | } | |
133 | hw = to_ish_hw(dev); | |
134 | dev->print_log = ish_event_tracer; | |
135 | ||
136 | /* mapping IO device memory */ | |
137 | hw->mem_addr = pci_iomap(pdev, 0, 0); | |
138 | if (!hw->mem_addr) { | |
139 | dev_err(&pdev->dev, "ISH: mapping I/O range failure\n"); | |
140 | ret = -ENOMEM; | |
141 | goto free_device; | |
142 | } | |
143 | ||
144 | dev->pdev = pdev; | |
145 | ||
146 | pdev->dev_flags |= PCI_DEV_FLAGS_NO_D3; | |
147 | ||
148 | /* request and enable interrupt */ | |
149 | ret = request_irq(pdev->irq, ish_irq_handler, IRQF_SHARED, | |
150 | KBUILD_MODNAME, dev); | |
151 | if (ret) { | |
152 | dev_err(&pdev->dev, "ISH: request IRQ failure (%d)\n", | |
153 | pdev->irq); | |
154 | goto free_device; | |
155 | } | |
156 | ||
157 | dev_set_drvdata(dev->devc, dev); | |
158 | ||
159 | init_waitqueue_head(&dev->suspend_wait); | |
160 | init_waitqueue_head(&dev->resume_wait); | |
161 | ||
162 | ret = ish_init(dev); | |
163 | if (ret) | |
164 | goto free_irq; | |
165 | ||
166 | return 0; | |
167 | ||
168 | free_irq: | |
169 | free_irq(pdev->irq, dev); | |
170 | free_device: | |
171 | pci_iounmap(pdev, hw->mem_addr); | |
172 | kfree(dev); | |
173 | release_regions: | |
174 | pci_release_regions(pdev); | |
175 | disable_device: | |
176 | pci_clear_master(pdev); | |
177 | pci_disable_device(pdev); | |
178 | dev_err(&pdev->dev, "ISH: PCI driver initialization failed.\n"); | |
179 | ||
180 | return ret; | |
181 | } | |
182 | ||
183 | /** | |
184 | * ish_remove() - PCI driver remove callback | |
185 | * @pdev: pci device | |
186 | * | |
187 | * This function does cleanup of ISH on pci remove callback | |
188 | */ | |
189 | static void ish_remove(struct pci_dev *pdev) | |
190 | { | |
191 | struct ishtp_device *ishtp_dev = pci_get_drvdata(pdev); | |
192 | struct ish_hw *hw = to_ish_hw(ishtp_dev); | |
193 | ||
194 | ishtp_bus_remove_all_clients(ishtp_dev, false); | |
195 | ish_device_disable(ishtp_dev); | |
196 | ||
197 | free_irq(pdev->irq, ishtp_dev); | |
198 | pci_iounmap(pdev, hw->mem_addr); | |
199 | pci_release_regions(pdev); | |
200 | pci_clear_master(pdev); | |
201 | pci_disable_device(pdev); | |
202 | kfree(ishtp_dev); | |
203 | } | |
204 | ||
205 | #ifdef CONFIG_PM | |
206 | static struct device *ish_resume_device; | |
207 | ||
208 | /** | |
209 | * ish_resume_handler() - Work function to complete resume | |
210 | * @work: work struct | |
211 | * | |
212 | * The resume work function to complete resume function asynchronously. | |
213 | * There are two types of platforms, one where ISH is not powered off, | |
214 | * in that case a simple resume message is enough, others we need | |
215 | * a reset sequence. | |
216 | */ | |
217 | static void ish_resume_handler(struct work_struct *work) | |
218 | { | |
219 | struct pci_dev *pdev = to_pci_dev(ish_resume_device); | |
220 | struct ishtp_device *dev = pci_get_drvdata(pdev); | |
221 | int ret; | |
222 | ||
223 | ishtp_send_resume(dev); | |
224 | ||
225 | /* 50 ms to get resume response */ | |
226 | if (dev->resume_flag) | |
227 | ret = wait_event_interruptible_timeout(dev->resume_wait, | |
228 | !dev->resume_flag, | |
229 | msecs_to_jiffies(50)); | |
230 | ||
231 | /* | |
232 | * If no resume response. This platform is not S0ix compatible | |
233 | * So on resume full reboot of ISH processor will happen, so | |
234 | * need to go through init sequence again | |
235 | */ | |
236 | if (dev->resume_flag) | |
237 | ish_init(dev); | |
238 | } | |
239 | ||
240 | /** | |
241 | * ish_suspend() - ISH suspend callback | |
242 | * @device: device pointer | |
243 | * | |
244 | * ISH suspend callback | |
245 | * | |
246 | * Return: 0 to the pm core | |
247 | */ | |
248 | static int ish_suspend(struct device *device) | |
249 | { | |
250 | struct pci_dev *pdev = to_pci_dev(device); | |
251 | struct ishtp_device *dev = pci_get_drvdata(pdev); | |
252 | ||
253 | enable_irq_wake(pdev->irq); | |
254 | /* | |
255 | * If previous suspend hasn't been asnwered then ISH is likely dead, | |
256 | * don't attempt nested notification | |
257 | */ | |
258 | if (dev->suspend_flag) | |
259 | return 0; | |
260 | ||
261 | dev->resume_flag = 0; | |
262 | dev->suspend_flag = 1; | |
263 | ishtp_send_suspend(dev); | |
264 | ||
265 | /* 25 ms should be enough for live ISH to flush all IPC buf */ | |
266 | if (dev->suspend_flag) | |
267 | wait_event_interruptible_timeout(dev->suspend_wait, | |
268 | !dev->suspend_flag, | |
269 | msecs_to_jiffies(25)); | |
270 | ||
271 | return 0; | |
272 | } | |
273 | ||
274 | static DECLARE_WORK(resume_work, ish_resume_handler); | |
275 | /** | |
276 | * ish_resume() - ISH resume callback | |
277 | * @device: device pointer | |
278 | * | |
279 | * ISH resume callback | |
280 | * | |
281 | * Return: 0 to the pm core | |
282 | */ | |
283 | static int ish_resume(struct device *device) | |
284 | { | |
285 | struct pci_dev *pdev = to_pci_dev(device); | |
286 | struct ishtp_device *dev = pci_get_drvdata(pdev); | |
287 | ||
288 | ish_resume_device = device; | |
289 | dev->resume_flag = 1; | |
290 | ||
291 | disable_irq_wake(pdev->irq); | |
292 | schedule_work(&resume_work); | |
293 | ||
294 | return 0; | |
295 | } | |
296 | ||
297 | static const struct dev_pm_ops ish_pm_ops = { | |
298 | .suspend = ish_suspend, | |
299 | .resume = ish_resume, | |
300 | }; | |
301 | #define ISHTP_ISH_PM_OPS (&ish_pm_ops) | |
302 | #else | |
303 | #define ISHTP_ISH_PM_OPS NULL | |
304 | #endif /* CONFIG_PM */ | |
305 | ||
306 | static struct pci_driver ish_driver = { | |
307 | .name = KBUILD_MODNAME, | |
308 | .id_table = ish_pci_tbl, | |
309 | .probe = ish_probe, | |
310 | .remove = ish_remove, | |
311 | .driver.pm = ISHTP_ISH_PM_OPS, | |
312 | }; | |
313 | ||
314 | module_pci_driver(ish_driver); | |
315 | ||
316 | /* Original author */ | |
317 | MODULE_AUTHOR("Daniel Drubin <daniel.drubin@intel.com>"); | |
318 | /* Adoption to upstream Linux kernel */ | |
319 | MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); | |
320 | ||
321 | MODULE_DESCRIPTION("Intel(R) Integrated Sensor Hub PCI Device Driver"); | |
322 | MODULE_LICENSE("GPL"); |