Commit | Line | Data |
---|---|---|
163d2089 VK |
1 | /* |
2 | * sst.c - Intel SST Driver for audio engine | |
3 | * | |
4 | * Copyright (C) 2008-14 Intel Corp | |
5 | * Authors: Vinod Koul <vinod.koul@intel.com> | |
6 | * Harsha Priya <priya.harsha@intel.com> | |
7 | * Dharageswari R <dharageswari.r@intel.com> | |
8 | * KP Jeeja <jeeja.kp@intel.com> | |
9 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
10 | * | |
11 | * This program is free software; you can redistribute it and/or modify | |
12 | * it under the terms of the GNU General Public License as published by | |
13 | * the Free Software Foundation; version 2 of the License. | |
14 | * | |
15 | * This program is distributed in the hope that it will be useful, but | |
16 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
18 | * General Public License for more details. | |
19 | * | |
20 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
21 | */ | |
22 | #include <linux/module.h> | |
23 | #include <linux/pci.h> | |
24 | #include <linux/fs.h> | |
25 | #include <linux/interrupt.h> | |
26 | #include <linux/firmware.h> | |
27 | #include <linux/pm_runtime.h> | |
28 | #include <linux/pm_qos.h> | |
29 | #include <linux/async.h> | |
30 | #include <linux/delay.h> | |
31 | #include <linux/acpi.h> | |
32 | #include <sound/core.h> | |
33 | #include <sound/pcm.h> | |
34 | #include <sound/soc.h> | |
35 | #include <sound/compress_driver.h> | |
36 | #include <asm/intel-mid.h> | |
37 | #include <asm/platform_sst_audio.h> | |
38 | #include "../sst-mfld-platform.h" | |
39 | #include "sst.h" | |
40 | #include "../sst-dsp.h" | |
41 | ||
42 | MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>"); | |
43 | MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>"); | |
44 | MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine Driver"); | |
45 | MODULE_LICENSE("GPL v2"); | |
46 | ||
47 | static inline bool sst_is_process_reply(u32 msg_id) | |
48 | { | |
49 | return ((msg_id & PROCESS_MSG) ? true : false); | |
50 | } | |
51 | ||
52 | static inline bool sst_validate_mailbox_size(unsigned int size) | |
53 | { | |
54 | return ((size <= SST_MAILBOX_SIZE) ? true : false); | |
55 | } | |
56 | ||
57 | static irqreturn_t intel_sst_interrupt_mrfld(int irq, void *context) | |
58 | { | |
59 | union interrupt_reg_mrfld isr; | |
60 | union ipc_header_mrfld header; | |
61 | union sst_imr_reg_mrfld imr; | |
62 | struct ipc_post *msg = NULL; | |
63 | unsigned int size = 0; | |
64 | struct intel_sst_drv *drv = (struct intel_sst_drv *) context; | |
65 | irqreturn_t retval = IRQ_HANDLED; | |
66 | ||
67 | /* Interrupt arrived, check src */ | |
68 | isr.full = sst_shim_read64(drv->shim, SST_ISRX); | |
69 | ||
70 | if (isr.part.done_interrupt) { | |
71 | /* Clear done bit */ | |
72 | spin_lock(&drv->ipc_spin_lock); | |
73 | header.full = sst_shim_read64(drv->shim, | |
74 | drv->ipc_reg.ipcx); | |
75 | header.p.header_high.part.done = 0; | |
76 | sst_shim_write64(drv->shim, drv->ipc_reg.ipcx, header.full); | |
77 | ||
78 | /* write 1 to clear status register */; | |
79 | isr.part.done_interrupt = 1; | |
80 | sst_shim_write64(drv->shim, SST_ISRX, isr.full); | |
81 | spin_unlock(&drv->ipc_spin_lock); | |
82 | ||
83 | /* we can send more messages to DSP so trigger work */ | |
84 | queue_work(drv->post_msg_wq, &drv->ipc_post_msg_wq); | |
85 | retval = IRQ_HANDLED; | |
86 | } | |
87 | ||
88 | if (isr.part.busy_interrupt) { | |
89 | /* message from dsp so copy that */ | |
90 | spin_lock(&drv->ipc_spin_lock); | |
91 | imr.full = sst_shim_read64(drv->shim, SST_IMRX); | |
92 | imr.part.busy_interrupt = 1; | |
93 | sst_shim_write64(drv->shim, SST_IMRX, imr.full); | |
94 | spin_unlock(&drv->ipc_spin_lock); | |
95 | header.full = sst_shim_read64(drv->shim, drv->ipc_reg.ipcd); | |
96 | ||
97 | if (sst_create_ipc_msg(&msg, header.p.header_high.part.large)) { | |
98 | drv->ops->clear_interrupt(drv); | |
99 | return IRQ_HANDLED; | |
100 | } | |
101 | ||
102 | if (header.p.header_high.part.large) { | |
103 | size = header.p.header_low_payload; | |
104 | if (sst_validate_mailbox_size(size)) { | |
105 | memcpy_fromio(msg->mailbox_data, | |
106 | drv->mailbox + drv->mailbox_recv_offset, size); | |
107 | } else { | |
108 | dev_err(drv->dev, | |
109 | "Mailbox not copied, payload size is: %u\n", size); | |
110 | header.p.header_low_payload = 0; | |
111 | } | |
112 | } | |
113 | ||
114 | msg->mrfld_header = header; | |
115 | msg->is_process_reply = | |
116 | sst_is_process_reply(header.p.header_high.part.msg_id); | |
117 | spin_lock(&drv->rx_msg_lock); | |
118 | list_add_tail(&msg->node, &drv->rx_list); | |
119 | spin_unlock(&drv->rx_msg_lock); | |
120 | drv->ops->clear_interrupt(drv); | |
121 | retval = IRQ_WAKE_THREAD; | |
122 | } | |
123 | return retval; | |
124 | } | |
125 | ||
126 | static irqreturn_t intel_sst_irq_thread_mrfld(int irq, void *context) | |
127 | { | |
128 | struct intel_sst_drv *drv = (struct intel_sst_drv *) context; | |
129 | struct ipc_post *__msg, *msg = NULL; | |
130 | unsigned long irq_flags; | |
131 | ||
132 | spin_lock_irqsave(&drv->rx_msg_lock, irq_flags); | |
133 | if (list_empty(&drv->rx_list)) { | |
134 | spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); | |
135 | return IRQ_HANDLED; | |
136 | } | |
137 | ||
138 | list_for_each_entry_safe(msg, __msg, &drv->rx_list, node) { | |
139 | list_del(&msg->node); | |
140 | spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); | |
141 | if (msg->is_process_reply) | |
142 | drv->ops->process_message(msg); | |
143 | else | |
144 | drv->ops->process_reply(drv, msg); | |
145 | ||
146 | if (msg->is_large) | |
147 | kfree(msg->mailbox_data); | |
148 | kfree(msg); | |
149 | spin_lock_irqsave(&drv->rx_msg_lock, irq_flags); | |
150 | } | |
151 | spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); | |
152 | return IRQ_HANDLED; | |
153 | } | |
154 | ||
d62f2a08 VK |
155 | static int sst_save_dsp_context_v2(struct intel_sst_drv *sst) |
156 | { | |
157 | int ret = 0; | |
158 | ||
159 | ret = sst_prepare_and_post_msg(sst, SST_TASK_ID_MEDIA, IPC_CMD, | |
160 | IPC_PREP_D3, PIPE_RSVD, 0, NULL, NULL, | |
161 | true, true, false, true); | |
162 | ||
163 | if (ret < 0) { | |
164 | dev_err(sst->dev, "not suspending FW!!, Err: %d\n", ret); | |
165 | return -EIO; | |
166 | } | |
167 | ||
168 | return 0; | |
169 | } | |
170 | ||
171 | ||
163d2089 VK |
172 | static struct intel_sst_ops mrfld_ops = { |
173 | .interrupt = intel_sst_interrupt_mrfld, | |
174 | .irq_thread = intel_sst_irq_thread_mrfld, | |
175 | .clear_interrupt = intel_sst_clear_intr_mrfld, | |
176 | .start = sst_start_mrfld, | |
177 | .reset = intel_sst_reset_dsp_mrfld, | |
178 | .post_message = sst_post_message_mrfld, | |
179 | .process_reply = sst_process_reply_mrfld, | |
d62f2a08 | 180 | .save_dsp_context = sst_save_dsp_context_v2, |
163d2089 VK |
181 | .alloc_stream = sst_alloc_stream_mrfld, |
182 | .post_download = sst_post_download_mrfld, | |
183 | }; | |
184 | ||
185 | int sst_driver_ops(struct intel_sst_drv *sst) | |
186 | { | |
187 | ||
39581031 | 188 | switch (sst->dev_id) { |
163d2089 VK |
189 | case SST_MRFLD_PCI_ID: |
190 | sst->tstamp = SST_TIME_STAMP_MRFLD; | |
191 | sst->ops = &mrfld_ops; | |
192 | return 0; | |
193 | ||
194 | default: | |
195 | dev_err(sst->dev, | |
39581031 | 196 | "SST Driver capablities missing for dev_id: %x", sst->dev_id); |
163d2089 VK |
197 | return -EINVAL; |
198 | }; | |
199 | } | |
200 | ||
201 | void sst_process_pending_msg(struct work_struct *work) | |
202 | { | |
203 | struct intel_sst_drv *ctx = container_of(work, | |
204 | struct intel_sst_drv, ipc_post_msg_wq); | |
205 | ||
206 | ctx->ops->post_message(ctx, NULL, false); | |
207 | } | |
208 | ||
209 | /* | |
210 | * intel_sst_probe - PCI probe function | |
211 | * | |
212 | * @pci: PCI device structure | |
213 | * @pci_id: PCI device ID structure | |
214 | * | |
215 | */ | |
216 | static int intel_sst_probe(struct pci_dev *pci, | |
217 | const struct pci_device_id *pci_id) | |
218 | { | |
219 | int i, ret = 0; | |
220 | struct intel_sst_drv *sst_drv_ctx; | |
221 | struct intel_sst_ops *ops; | |
222 | struct sst_platform_info *sst_pdata = pci->dev.platform_data; | |
223 | int ddr_base; | |
224 | ||
225 | dev_dbg(&pci->dev, "Probe for DID %x\n", pci->device); | |
226 | sst_drv_ctx = devm_kzalloc(&pci->dev, sizeof(*sst_drv_ctx), GFP_KERNEL); | |
227 | if (!sst_drv_ctx) | |
228 | return -ENOMEM; | |
229 | ||
230 | sst_drv_ctx->dev = &pci->dev; | |
39581031 | 231 | sst_drv_ctx->dev_id = pci->device; |
163d2089 VK |
232 | if (!sst_pdata) |
233 | return -EINVAL; | |
234 | ||
235 | sst_drv_ctx->pdata = sst_pdata; | |
236 | if (!sst_drv_ctx->pdata->probe_data) | |
237 | return -EINVAL; | |
238 | ||
239 | memcpy(&sst_drv_ctx->info, sst_drv_ctx->pdata->probe_data, | |
240 | sizeof(sst_drv_ctx->info)); | |
241 | ||
242 | if (0 != sst_driver_ops(sst_drv_ctx)) | |
243 | return -EINVAL; | |
244 | ||
245 | ops = sst_drv_ctx->ops; | |
246 | mutex_init(&sst_drv_ctx->sst_lock); | |
247 | ||
248 | /* pvt_id 0 reserved for async messages */ | |
249 | sst_drv_ctx->pvt_id = 1; | |
250 | sst_drv_ctx->stream_cnt = 0; | |
251 | sst_drv_ctx->fw_in_mem = NULL; | |
252 | ||
253 | /* we use memcpy, so set to 0 */ | |
254 | sst_drv_ctx->use_dma = 0; | |
255 | sst_drv_ctx->use_lli = 0; | |
256 | ||
257 | INIT_LIST_HEAD(&sst_drv_ctx->memcpy_list); | |
258 | INIT_LIST_HEAD(&sst_drv_ctx->ipc_dispatch_list); | |
259 | INIT_LIST_HEAD(&sst_drv_ctx->block_list); | |
260 | INIT_LIST_HEAD(&sst_drv_ctx->rx_list); | |
261 | ||
262 | sst_drv_ctx->post_msg_wq = | |
263 | create_singlethread_workqueue("sst_post_msg_wq"); | |
264 | if (!sst_drv_ctx->post_msg_wq) { | |
265 | ret = -EINVAL; | |
266 | goto do_free_drv_ctx; | |
267 | } | |
268 | INIT_WORK(&sst_drv_ctx->ipc_post_msg_wq, sst_process_pending_msg); | |
269 | init_waitqueue_head(&sst_drv_ctx->wait_queue); | |
270 | ||
271 | spin_lock_init(&sst_drv_ctx->ipc_spin_lock); | |
272 | spin_lock_init(&sst_drv_ctx->block_lock); | |
273 | spin_lock_init(&sst_drv_ctx->rx_msg_lock); | |
274 | ||
275 | dev_info(sst_drv_ctx->dev, "Got drv data max stream %d\n", | |
276 | sst_drv_ctx->info.max_streams); | |
277 | for (i = 1; i <= sst_drv_ctx->info.max_streams; i++) { | |
278 | struct stream_info *stream = &sst_drv_ctx->streams[i]; | |
279 | ||
280 | memset(stream, 0, sizeof(*stream)); | |
281 | stream->pipe_id = PIPE_RSVD; | |
282 | mutex_init(&stream->lock); | |
283 | } | |
284 | ||
285 | /* Init the device */ | |
286 | ret = pcim_enable_device(pci); | |
287 | if (ret) { | |
288 | dev_err(sst_drv_ctx->dev, | |
289 | "device can't be enabled. Returned err: %d\n", ret); | |
290 | goto do_free_mem; | |
291 | } | |
292 | sst_drv_ctx->pci = pci_dev_get(pci); | |
293 | ret = pci_request_regions(pci, SST_DRV_NAME); | |
294 | if (ret) | |
295 | goto do_free_mem; | |
296 | ||
297 | /* map registers */ | |
298 | /* DDR base */ | |
39581031 | 299 | if (sst_drv_ctx->dev_id == SST_MRFLD_PCI_ID) { |
163d2089 VK |
300 | sst_drv_ctx->ddr_base = pci_resource_start(pci, 0); |
301 | /* check that the relocated IMR base matches with FW Binary */ | |
302 | ddr_base = relocate_imr_addr_mrfld(sst_drv_ctx->ddr_base); | |
303 | if (!sst_drv_ctx->pdata->lib_info) { | |
304 | dev_err(sst_drv_ctx->dev, "lib_info pointer NULL\n"); | |
305 | ret = -EINVAL; | |
306 | goto do_release_regions; | |
307 | } | |
308 | if (ddr_base != sst_drv_ctx->pdata->lib_info->mod_base) { | |
309 | dev_err(sst_drv_ctx->dev, | |
310 | "FW LSP DDR BASE does not match with IFWI\n"); | |
311 | ret = -EINVAL; | |
312 | goto do_release_regions; | |
313 | } | |
314 | sst_drv_ctx->ddr_end = pci_resource_end(pci, 0); | |
315 | ||
316 | sst_drv_ctx->ddr = pcim_iomap(pci, 0, | |
317 | pci_resource_len(pci, 0)); | |
318 | if (!sst_drv_ctx->ddr) { | |
319 | ret = -EINVAL; | |
320 | goto do_release_regions; | |
321 | } | |
322 | dev_dbg(sst_drv_ctx->dev, "sst: DDR Ptr %p\n", sst_drv_ctx->ddr); | |
323 | } else { | |
324 | sst_drv_ctx->ddr = NULL; | |
325 | } | |
326 | ||
327 | /* SHIM */ | |
328 | sst_drv_ctx->shim_phy_add = pci_resource_start(pci, 1); | |
329 | sst_drv_ctx->shim = pcim_iomap(pci, 1, pci_resource_len(pci, 1)); | |
330 | if (!sst_drv_ctx->shim) { | |
331 | ret = -EINVAL; | |
332 | goto do_release_regions; | |
333 | } | |
334 | dev_dbg(sst_drv_ctx->dev, "SST Shim Ptr %p\n", sst_drv_ctx->shim); | |
335 | ||
336 | /* Shared SRAM */ | |
337 | sst_drv_ctx->mailbox_add = pci_resource_start(pci, 2); | |
338 | sst_drv_ctx->mailbox = pcim_iomap(pci, 2, pci_resource_len(pci, 2)); | |
339 | if (!sst_drv_ctx->mailbox) { | |
340 | ret = -EINVAL; | |
341 | goto do_release_regions; | |
342 | } | |
343 | dev_dbg(sst_drv_ctx->dev, "SRAM Ptr %p\n", sst_drv_ctx->mailbox); | |
344 | ||
345 | /* IRAM */ | |
346 | sst_drv_ctx->iram_end = pci_resource_end(pci, 3); | |
347 | sst_drv_ctx->iram_base = pci_resource_start(pci, 3); | |
348 | sst_drv_ctx->iram = pcim_iomap(pci, 3, pci_resource_len(pci, 3)); | |
349 | if (!sst_drv_ctx->iram) { | |
350 | ret = -EINVAL; | |
351 | goto do_release_regions; | |
352 | } | |
353 | dev_dbg(sst_drv_ctx->dev, "IRAM Ptr %p\n", sst_drv_ctx->iram); | |
354 | ||
355 | /* DRAM */ | |
356 | sst_drv_ctx->dram_end = pci_resource_end(pci, 4); | |
357 | sst_drv_ctx->dram_base = pci_resource_start(pci, 4); | |
358 | sst_drv_ctx->dram = pcim_iomap(pci, 4, pci_resource_len(pci, 4)); | |
359 | if (!sst_drv_ctx->dram) { | |
360 | ret = -EINVAL; | |
361 | goto do_release_regions; | |
362 | } | |
363 | dev_dbg(sst_drv_ctx->dev, "DRAM Ptr %p\n", sst_drv_ctx->dram); | |
364 | ||
365 | ||
366 | sst_set_fw_state_locked(sst_drv_ctx, SST_RESET); | |
367 | sst_drv_ctx->irq_num = pci->irq; | |
368 | /* Register the ISR */ | |
369 | ret = devm_request_threaded_irq(&pci->dev, pci->irq, | |
370 | sst_drv_ctx->ops->interrupt, | |
371 | sst_drv_ctx->ops->irq_thread, 0, SST_DRV_NAME, | |
372 | sst_drv_ctx); | |
373 | if (ret) | |
374 | goto do_release_regions; | |
375 | dev_dbg(sst_drv_ctx->dev, "Registered IRQ 0x%x\n", pci->irq); | |
376 | ||
377 | /* default intr are unmasked so set this as masked */ | |
39581031 | 378 | if (sst_drv_ctx->dev_id == SST_MRFLD_PCI_ID) |
163d2089 VK |
379 | sst_shim_write64(sst_drv_ctx->shim, SST_IMRX, 0xFFFF0038); |
380 | ||
381 | pci_set_drvdata(pci, sst_drv_ctx); | |
382 | pm_runtime_set_autosuspend_delay(sst_drv_ctx->dev, SST_SUSPEND_DELAY); | |
383 | pm_runtime_use_autosuspend(sst_drv_ctx->dev); | |
384 | pm_runtime_allow(sst_drv_ctx->dev); | |
385 | pm_runtime_put_noidle(sst_drv_ctx->dev); | |
386 | sst_register(sst_drv_ctx->dev); | |
387 | sst_drv_ctx->qos = devm_kzalloc(&pci->dev, | |
388 | sizeof(struct pm_qos_request), GFP_KERNEL); | |
389 | if (!sst_drv_ctx->qos) { | |
390 | ret = -ENOMEM; | |
391 | goto do_release_regions; | |
392 | } | |
393 | pm_qos_add_request(sst_drv_ctx->qos, PM_QOS_CPU_DMA_LATENCY, | |
394 | PM_QOS_DEFAULT_VALUE); | |
395 | ||
396 | return ret; | |
397 | ||
398 | do_release_regions: | |
399 | pci_release_regions(pci); | |
400 | do_free_mem: | |
401 | destroy_workqueue(sst_drv_ctx->post_msg_wq); | |
402 | do_free_drv_ctx: | |
403 | dev_err(sst_drv_ctx->dev, "Probe failed with %d\n", ret); | |
404 | return ret; | |
405 | } | |
406 | ||
407 | /** | |
408 | * intel_sst_remove - PCI remove function | |
409 | * | |
410 | * @pci: PCI device structure | |
411 | * | |
412 | * This function is called by OS when a device is unloaded | |
413 | * This frees the interrupt etc | |
414 | */ | |
415 | static void intel_sst_remove(struct pci_dev *pci) | |
416 | { | |
417 | struct intel_sst_drv *sst_drv_ctx = pci_get_drvdata(pci); | |
418 | ||
419 | pm_runtime_get_noresume(sst_drv_ctx->dev); | |
420 | pm_runtime_forbid(sst_drv_ctx->dev); | |
421 | sst_unregister(sst_drv_ctx->dev); | |
422 | pci_dev_put(sst_drv_ctx->pci); | |
423 | sst_set_fw_state_locked(sst_drv_ctx, SST_SHUTDOWN); | |
424 | ||
425 | flush_scheduled_work(); | |
426 | destroy_workqueue(sst_drv_ctx->post_msg_wq); | |
427 | pm_qos_remove_request(sst_drv_ctx->qos); | |
428 | kfree(sst_drv_ctx->fw_sg_list.src); | |
429 | kfree(sst_drv_ctx->fw_sg_list.dst); | |
430 | sst_drv_ctx->fw_sg_list.list_len = 0; | |
431 | kfree(sst_drv_ctx->fw_in_mem); | |
432 | sst_drv_ctx->fw_in_mem = NULL; | |
433 | sst_memcpy_free_resources(sst_drv_ctx); | |
434 | sst_drv_ctx = NULL; | |
435 | pci_release_regions(pci); | |
436 | pci_set_drvdata(pci, NULL); | |
437 | } | |
438 | ||
d62f2a08 VK |
439 | static int intel_sst_runtime_suspend(struct device *dev) |
440 | { | |
441 | int ret = 0; | |
442 | struct intel_sst_drv *ctx = dev_get_drvdata(dev); | |
443 | ||
444 | if (ctx->sst_state == SST_RESET) { | |
445 | dev_dbg(dev, "LPE is already in RESET state, No action\n"); | |
446 | return 0; | |
447 | } | |
448 | /* save fw context */ | |
449 | if (ctx->ops->save_dsp_context(ctx)) | |
450 | return -EBUSY; | |
451 | ||
452 | /* Move the SST state to Reset */ | |
453 | sst_set_fw_state_locked(ctx, SST_RESET); | |
454 | ||
455 | synchronize_irq(ctx->irq_num); | |
456 | flush_workqueue(ctx->post_msg_wq); | |
457 | ||
458 | return ret; | |
459 | } | |
460 | ||
461 | static int intel_sst_runtime_resume(struct device *dev) | |
462 | { | |
463 | int ret = 0; | |
464 | struct intel_sst_drv *ctx = dev_get_drvdata(dev); | |
465 | ||
466 | mutex_lock(&ctx->sst_lock); | |
467 | if (ctx->sst_state == SST_RESET) { | |
468 | ret = sst_load_fw(ctx); | |
469 | if (ret) { | |
470 | dev_err(dev, "FW download fail %d\n", ret); | |
471 | ctx->sst_state = SST_RESET; | |
472 | } | |
473 | } | |
474 | mutex_unlock(&ctx->sst_lock); | |
475 | return ret; | |
476 | } | |
477 | ||
478 | static const struct dev_pm_ops intel_sst_pm = { | |
479 | .runtime_suspend = intel_sst_runtime_suspend, | |
480 | .runtime_resume = intel_sst_runtime_resume, | |
481 | }; | |
482 | ||
163d2089 VK |
483 | /* PCI Routines */ |
484 | static struct pci_device_id intel_sst_ids[] = { | |
485 | { PCI_VDEVICE(INTEL, SST_MRFLD_PCI_ID), 0}, | |
486 | { 0, } | |
487 | }; | |
488 | ||
489 | static struct pci_driver sst_driver = { | |
490 | .name = SST_DRV_NAME, | |
491 | .id_table = intel_sst_ids, | |
492 | .probe = intel_sst_probe, | |
493 | .remove = intel_sst_remove, | |
d62f2a08 VK |
494 | #ifdef CONFIG_PM |
495 | .driver = { | |
496 | .pm = &intel_sst_pm, | |
497 | }, | |
498 | #endif | |
163d2089 VK |
499 | }; |
500 | ||
501 | module_pci_driver(sst_driver); |