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> | |
163d2089 VK |
23 | #include <linux/fs.h> |
24 | #include <linux/interrupt.h> | |
25 | #include <linux/firmware.h> | |
26 | #include <linux/pm_runtime.h> | |
27 | #include <linux/pm_qos.h> | |
28 | #include <linux/async.h> | |
336cfbb0 | 29 | #include <linux/acpi.h> |
163d2089 | 30 | #include <sound/core.h> |
163d2089 | 31 | #include <sound/soc.h> |
163d2089 VK |
32 | #include <asm/platform_sst_audio.h> |
33 | #include "../sst-mfld-platform.h" | |
34 | #include "sst.h" | |
35 | #include "../sst-dsp.h" | |
36 | ||
37 | MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>"); | |
38 | MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>"); | |
39 | MODULE_DESCRIPTION("Intel (R) SST(R) Audio Engine Driver"); | |
40 | MODULE_LICENSE("GPL v2"); | |
41 | ||
42 | static inline bool sst_is_process_reply(u32 msg_id) | |
43 | { | |
44 | return ((msg_id & PROCESS_MSG) ? true : false); | |
45 | } | |
46 | ||
47 | static inline bool sst_validate_mailbox_size(unsigned int size) | |
48 | { | |
49 | return ((size <= SST_MAILBOX_SIZE) ? true : false); | |
50 | } | |
51 | ||
52 | static irqreturn_t intel_sst_interrupt_mrfld(int irq, void *context) | |
53 | { | |
54 | union interrupt_reg_mrfld isr; | |
55 | union ipc_header_mrfld header; | |
56 | union sst_imr_reg_mrfld imr; | |
57 | struct ipc_post *msg = NULL; | |
58 | unsigned int size = 0; | |
59 | struct intel_sst_drv *drv = (struct intel_sst_drv *) context; | |
60 | irqreturn_t retval = IRQ_HANDLED; | |
61 | ||
62 | /* Interrupt arrived, check src */ | |
63 | isr.full = sst_shim_read64(drv->shim, SST_ISRX); | |
64 | ||
65 | if (isr.part.done_interrupt) { | |
66 | /* Clear done bit */ | |
67 | spin_lock(&drv->ipc_spin_lock); | |
68 | header.full = sst_shim_read64(drv->shim, | |
69 | drv->ipc_reg.ipcx); | |
70 | header.p.header_high.part.done = 0; | |
71 | sst_shim_write64(drv->shim, drv->ipc_reg.ipcx, header.full); | |
72 | ||
73 | /* write 1 to clear status register */; | |
74 | isr.part.done_interrupt = 1; | |
75 | sst_shim_write64(drv->shim, SST_ISRX, isr.full); | |
76 | spin_unlock(&drv->ipc_spin_lock); | |
77 | ||
78 | /* we can send more messages to DSP so trigger work */ | |
79 | queue_work(drv->post_msg_wq, &drv->ipc_post_msg_wq); | |
80 | retval = IRQ_HANDLED; | |
81 | } | |
82 | ||
83 | if (isr.part.busy_interrupt) { | |
84 | /* message from dsp so copy that */ | |
85 | spin_lock(&drv->ipc_spin_lock); | |
86 | imr.full = sst_shim_read64(drv->shim, SST_IMRX); | |
87 | imr.part.busy_interrupt = 1; | |
88 | sst_shim_write64(drv->shim, SST_IMRX, imr.full); | |
89 | spin_unlock(&drv->ipc_spin_lock); | |
90 | header.full = sst_shim_read64(drv->shim, drv->ipc_reg.ipcd); | |
91 | ||
92 | if (sst_create_ipc_msg(&msg, header.p.header_high.part.large)) { | |
93 | drv->ops->clear_interrupt(drv); | |
94 | return IRQ_HANDLED; | |
95 | } | |
96 | ||
97 | if (header.p.header_high.part.large) { | |
98 | size = header.p.header_low_payload; | |
99 | if (sst_validate_mailbox_size(size)) { | |
100 | memcpy_fromio(msg->mailbox_data, | |
101 | drv->mailbox + drv->mailbox_recv_offset, size); | |
102 | } else { | |
103 | dev_err(drv->dev, | |
104 | "Mailbox not copied, payload size is: %u\n", size); | |
105 | header.p.header_low_payload = 0; | |
106 | } | |
107 | } | |
108 | ||
109 | msg->mrfld_header = header; | |
110 | msg->is_process_reply = | |
111 | sst_is_process_reply(header.p.header_high.part.msg_id); | |
112 | spin_lock(&drv->rx_msg_lock); | |
113 | list_add_tail(&msg->node, &drv->rx_list); | |
114 | spin_unlock(&drv->rx_msg_lock); | |
115 | drv->ops->clear_interrupt(drv); | |
116 | retval = IRQ_WAKE_THREAD; | |
117 | } | |
118 | return retval; | |
119 | } | |
120 | ||
121 | static irqreturn_t intel_sst_irq_thread_mrfld(int irq, void *context) | |
122 | { | |
123 | struct intel_sst_drv *drv = (struct intel_sst_drv *) context; | |
124 | struct ipc_post *__msg, *msg = NULL; | |
125 | unsigned long irq_flags; | |
126 | ||
127 | spin_lock_irqsave(&drv->rx_msg_lock, irq_flags); | |
128 | if (list_empty(&drv->rx_list)) { | |
129 | spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); | |
130 | return IRQ_HANDLED; | |
131 | } | |
132 | ||
133 | list_for_each_entry_safe(msg, __msg, &drv->rx_list, node) { | |
134 | list_del(&msg->node); | |
135 | spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); | |
136 | if (msg->is_process_reply) | |
137 | drv->ops->process_message(msg); | |
138 | else | |
139 | drv->ops->process_reply(drv, msg); | |
140 | ||
141 | if (msg->is_large) | |
142 | kfree(msg->mailbox_data); | |
143 | kfree(msg); | |
144 | spin_lock_irqsave(&drv->rx_msg_lock, irq_flags); | |
145 | } | |
146 | spin_unlock_irqrestore(&drv->rx_msg_lock, irq_flags); | |
147 | return IRQ_HANDLED; | |
148 | } | |
149 | ||
d62f2a08 VK |
150 | static int sst_save_dsp_context_v2(struct intel_sst_drv *sst) |
151 | { | |
152 | int ret = 0; | |
153 | ||
154 | ret = sst_prepare_and_post_msg(sst, SST_TASK_ID_MEDIA, IPC_CMD, | |
155 | IPC_PREP_D3, PIPE_RSVD, 0, NULL, NULL, | |
156 | true, true, false, true); | |
157 | ||
158 | if (ret < 0) { | |
159 | dev_err(sst->dev, "not suspending FW!!, Err: %d\n", ret); | |
160 | return -EIO; | |
161 | } | |
162 | ||
163 | return 0; | |
164 | } | |
165 | ||
166 | ||
163d2089 VK |
167 | static struct intel_sst_ops mrfld_ops = { |
168 | .interrupt = intel_sst_interrupt_mrfld, | |
169 | .irq_thread = intel_sst_irq_thread_mrfld, | |
170 | .clear_interrupt = intel_sst_clear_intr_mrfld, | |
171 | .start = sst_start_mrfld, | |
172 | .reset = intel_sst_reset_dsp_mrfld, | |
173 | .post_message = sst_post_message_mrfld, | |
174 | .process_reply = sst_process_reply_mrfld, | |
d62f2a08 | 175 | .save_dsp_context = sst_save_dsp_context_v2, |
163d2089 VK |
176 | .alloc_stream = sst_alloc_stream_mrfld, |
177 | .post_download = sst_post_download_mrfld, | |
178 | }; | |
179 | ||
180 | int sst_driver_ops(struct intel_sst_drv *sst) | |
181 | { | |
182 | ||
39581031 | 183 | switch (sst->dev_id) { |
163d2089 | 184 | case SST_MRFLD_PCI_ID: |
336cfbb0 | 185 | case SST_BYT_ACPI_ID: |
163d2089 VK |
186 | sst->tstamp = SST_TIME_STAMP_MRFLD; |
187 | sst->ops = &mrfld_ops; | |
188 | return 0; | |
189 | ||
190 | default: | |
191 | dev_err(sst->dev, | |
39581031 | 192 | "SST Driver capablities missing for dev_id: %x", sst->dev_id); |
163d2089 VK |
193 | return -EINVAL; |
194 | }; | |
195 | } | |
196 | ||
197 | void sst_process_pending_msg(struct work_struct *work) | |
198 | { | |
199 | struct intel_sst_drv *ctx = container_of(work, | |
200 | struct intel_sst_drv, ipc_post_msg_wq); | |
201 | ||
202 | ctx->ops->post_message(ctx, NULL, false); | |
203 | } | |
204 | ||
7e73e4d8 SP |
205 | static int sst_workqueue_init(struct intel_sst_drv *ctx) |
206 | { | |
207 | INIT_LIST_HEAD(&ctx->memcpy_list); | |
208 | INIT_LIST_HEAD(&ctx->rx_list); | |
209 | INIT_LIST_HEAD(&ctx->ipc_dispatch_list); | |
210 | INIT_LIST_HEAD(&ctx->block_list); | |
211 | INIT_WORK(&ctx->ipc_post_msg_wq, sst_process_pending_msg); | |
212 | init_waitqueue_head(&ctx->wait_queue); | |
213 | ||
214 | ctx->post_msg_wq = | |
215 | create_singlethread_workqueue("sst_post_msg_wq"); | |
216 | if (!ctx->post_msg_wq) | |
217 | return -EBUSY; | |
218 | return 0; | |
219 | } | |
220 | ||
54adc0ad SP |
221 | static void sst_init_locks(struct intel_sst_drv *ctx) |
222 | { | |
223 | mutex_init(&ctx->sst_lock); | |
224 | spin_lock_init(&ctx->rx_msg_lock); | |
225 | spin_lock_init(&ctx->ipc_spin_lock); | |
226 | spin_lock_init(&ctx->block_lock); | |
227 | } | |
228 | ||
2559d992 SP |
229 | int sst_alloc_drv_context(struct intel_sst_drv **ctx, |
230 | struct device *dev, unsigned int dev_id) | |
231 | { | |
232 | *ctx = devm_kzalloc(dev, sizeof(struct intel_sst_drv), GFP_KERNEL); | |
233 | if (!(*ctx)) | |
234 | return -ENOMEM; | |
235 | ||
236 | (*ctx)->dev = dev; | |
237 | (*ctx)->dev_id = dev_id; | |
238 | ||
239 | return 0; | |
240 | } | |
f533a035 | 241 | EXPORT_SYMBOL_GPL(sst_alloc_drv_context); |
2559d992 | 242 | |
250454d8 SP |
243 | int sst_context_init(struct intel_sst_drv *ctx) |
244 | { | |
245 | int ret = 0, i; | |
246 | ||
247 | if (!ctx->pdata) | |
248 | return -EINVAL; | |
249 | ||
250 | if (!ctx->pdata->probe_data) | |
251 | return -EINVAL; | |
252 | ||
253 | memcpy(&ctx->info, ctx->pdata->probe_data, sizeof(ctx->info)); | |
254 | ||
255 | ret = sst_driver_ops(ctx); | |
256 | if (ret != 0) | |
257 | return -EINVAL; | |
258 | ||
259 | sst_init_locks(ctx); | |
f533a035 | 260 | sst_set_fw_state_locked(ctx, SST_RESET); |
250454d8 SP |
261 | |
262 | /* pvt_id 0 reserved for async messages */ | |
263 | ctx->pvt_id = 1; | |
264 | ctx->stream_cnt = 0; | |
265 | ctx->fw_in_mem = NULL; | |
266 | /* we use memcpy, so set to 0 */ | |
267 | ctx->use_dma = 0; | |
268 | ctx->use_lli = 0; | |
269 | ||
270 | if (sst_workqueue_init(ctx)) | |
271 | return -EINVAL; | |
272 | ||
273 | ctx->mailbox_recv_offset = ctx->pdata->ipc_info->mbox_recv_off; | |
274 | ctx->ipc_reg.ipcx = SST_IPCX + ctx->pdata->ipc_info->ipc_offset; | |
275 | ctx->ipc_reg.ipcd = SST_IPCD + ctx->pdata->ipc_info->ipc_offset; | |
276 | ||
277 | dev_info(ctx->dev, "Got drv data max stream %d\n", | |
278 | ctx->info.max_streams); | |
279 | ||
280 | for (i = 1; i <= ctx->info.max_streams; i++) { | |
281 | struct stream_info *stream = &ctx->streams[i]; | |
282 | ||
283 | memset(stream, 0, sizeof(*stream)); | |
284 | stream->pipe_id = PIPE_RSVD; | |
285 | mutex_init(&stream->lock); | |
286 | } | |
287 | ||
288 | /* Register the ISR */ | |
289 | ret = devm_request_threaded_irq(ctx->dev, ctx->irq_num, ctx->ops->interrupt, | |
290 | ctx->ops->irq_thread, 0, SST_DRV_NAME, | |
291 | ctx); | |
292 | if (ret) | |
293 | goto do_free_mem; | |
294 | ||
295 | dev_dbg(ctx->dev, "Registered IRQ %#x\n", ctx->irq_num); | |
296 | ||
297 | /* default intr are unmasked so set this as masked */ | |
298 | sst_shim_write64(ctx->shim, SST_IMRX, 0xFFFF0038); | |
299 | ||
300 | ctx->qos = devm_kzalloc(ctx->dev, | |
301 | sizeof(struct pm_qos_request), GFP_KERNEL); | |
302 | if (!ctx->qos) { | |
303 | ret = -ENOMEM; | |
304 | goto do_free_mem; | |
305 | } | |
306 | pm_qos_add_request(ctx->qos, PM_QOS_CPU_DMA_LATENCY, | |
307 | PM_QOS_DEFAULT_VALUE); | |
f533a035 VK |
308 | |
309 | dev_dbg(ctx->dev, "Requesting FW %s now...\n", ctx->firmware_name); | |
310 | ret = request_firmware_nowait(THIS_MODULE, true, ctx->firmware_name, | |
311 | ctx->dev, GFP_KERNEL, ctx, sst_firmware_load_cb); | |
312 | if (ret) { | |
313 | dev_err(ctx->dev, "Firmware download failed:%d\n", ret); | |
314 | goto do_free_mem; | |
315 | } | |
316 | sst_register(ctx->dev); | |
250454d8 SP |
317 | return 0; |
318 | ||
319 | do_free_mem: | |
320 | destroy_workqueue(ctx->post_msg_wq); | |
321 | return ret; | |
322 | } | |
f533a035 | 323 | EXPORT_SYMBOL_GPL(sst_context_init); |
250454d8 SP |
324 | |
325 | void sst_context_cleanup(struct intel_sst_drv *ctx) | |
326 | { | |
327 | pm_runtime_get_noresume(ctx->dev); | |
336cfbb0 | 328 | pm_runtime_disable(ctx->dev); |
250454d8 SP |
329 | sst_unregister(ctx->dev); |
330 | sst_set_fw_state_locked(ctx, SST_SHUTDOWN); | |
331 | flush_scheduled_work(); | |
332 | destroy_workqueue(ctx->post_msg_wq); | |
333 | pm_qos_remove_request(ctx->qos); | |
334 | kfree(ctx->fw_sg_list.src); | |
335 | kfree(ctx->fw_sg_list.dst); | |
336 | ctx->fw_sg_list.list_len = 0; | |
337 | kfree(ctx->fw_in_mem); | |
338 | ctx->fw_in_mem = NULL; | |
339 | sst_memcpy_free_resources(ctx); | |
340 | ctx = NULL; | |
341 | } | |
f533a035 | 342 | EXPORT_SYMBOL_GPL(sst_context_cleanup); |
2559d992 | 343 | |
b0d94acd VK |
344 | static inline void sst_save_shim64(struct intel_sst_drv *ctx, |
345 | void __iomem *shim, | |
346 | struct sst_shim_regs64 *shim_regs) | |
347 | { | |
348 | unsigned long irq_flags; | |
349 | ||
350 | spin_lock_irqsave(&ctx->ipc_spin_lock, irq_flags); | |
351 | ||
352 | shim_regs->imrx = sst_shim_read64(shim, SST_IMRX), | |
353 | ||
354 | spin_unlock_irqrestore(&ctx->ipc_spin_lock, irq_flags); | |
355 | } | |
356 | ||
357 | static inline void sst_restore_shim64(struct intel_sst_drv *ctx, | |
358 | void __iomem *shim, | |
359 | struct sst_shim_regs64 *shim_regs) | |
360 | { | |
361 | unsigned long irq_flags; | |
362 | ||
363 | /* | |
364 | * we only need to restore IMRX for this case, rest will be | |
365 | * initialize by FW or driver when firmware is loaded | |
366 | */ | |
367 | spin_lock_irqsave(&ctx->ipc_spin_lock, irq_flags); | |
368 | sst_shim_write64(shim, SST_IMRX, shim_regs->imrx), | |
369 | spin_unlock_irqrestore(&ctx->ipc_spin_lock, irq_flags); | |
370 | } | |
371 | ||
7fb73c74 | 372 | void sst_configure_runtime_pm(struct intel_sst_drv *ctx) |
163d2089 | 373 | { |
7fb73c74 SP |
374 | pm_runtime_set_autosuspend_delay(ctx->dev, SST_SUSPEND_DELAY); |
375 | pm_runtime_use_autosuspend(ctx->dev); | |
336cfbb0 VK |
376 | /* |
377 | * For acpi devices, the actual physical device state is | |
378 | * initially active. So change the state to active before | |
379 | * enabling the pm | |
380 | */ | |
381 | if (acpi_disabled) { | |
382 | pm_runtime_set_active(ctx->dev); | |
383 | pm_runtime_enable(ctx->dev); | |
384 | } else { | |
385 | pm_runtime_allow(ctx->dev); | |
386 | pm_runtime_put_noidle(ctx->dev); | |
387 | } | |
388 | sst_save_shim64(ctx, ctx->shim, ctx->shim_regs64); | |
7fb73c74 | 389 | } |
f533a035 | 390 | EXPORT_SYMBOL_GPL(sst_configure_runtime_pm); |
163d2089 | 391 | |
d62f2a08 VK |
392 | static int intel_sst_runtime_suspend(struct device *dev) |
393 | { | |
394 | int ret = 0; | |
395 | struct intel_sst_drv *ctx = dev_get_drvdata(dev); | |
396 | ||
397 | if (ctx->sst_state == SST_RESET) { | |
398 | dev_dbg(dev, "LPE is already in RESET state, No action\n"); | |
399 | return 0; | |
400 | } | |
401 | /* save fw context */ | |
402 | if (ctx->ops->save_dsp_context(ctx)) | |
403 | return -EBUSY; | |
404 | ||
405 | /* Move the SST state to Reset */ | |
406 | sst_set_fw_state_locked(ctx, SST_RESET); | |
407 | ||
408 | synchronize_irq(ctx->irq_num); | |
409 | flush_workqueue(ctx->post_msg_wq); | |
410 | ||
336cfbb0 VK |
411 | /* save the shim registers because PMC doesn't save state */ |
412 | sst_save_shim64(ctx, ctx->shim, ctx->shim_regs64); | |
413 | ||
d62f2a08 VK |
414 | return ret; |
415 | } | |
416 | ||
417 | static int intel_sst_runtime_resume(struct device *dev) | |
418 | { | |
419 | int ret = 0; | |
420 | struct intel_sst_drv *ctx = dev_get_drvdata(dev); | |
421 | ||
d62f2a08 VK |
422 | if (ctx->sst_state == SST_RESET) { |
423 | ret = sst_load_fw(ctx); | |
424 | if (ret) { | |
425 | dev_err(dev, "FW download fail %d\n", ret); | |
45f31bfc | 426 | sst_set_fw_state_locked(ctx, SST_RESET); |
d62f2a08 VK |
427 | } |
428 | } | |
d62f2a08 VK |
429 | return ret; |
430 | } | |
431 | ||
f533a035 | 432 | const struct dev_pm_ops intel_sst_pm = { |
d62f2a08 VK |
433 | .runtime_suspend = intel_sst_runtime_suspend, |
434 | .runtime_resume = intel_sst_runtime_resume, | |
435 | }; | |
f533a035 | 436 | EXPORT_SYMBOL_GPL(intel_sst_pm); |