| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 |
|
| 11 | |
| 12 | * Hardware interface for audio DSP on Atom devices
|
| 13 |
|
| 14 |
|
| 15 | #include <linux/module.h>
|
| 16 | #include <sound/sof.h>
|
| 17 | #include <sound/sof/xtensa.h>
|
| 18 | #include <sound/soc-acpi.h>
|
| 19 | #include <sound/soc-acpi-intel-match.h>
|
| 20 | #include <sound/intel-dsp-config.h>
|
| 21 | #include "../ops.h"
|
| 22 | #include "shim.h"
|
| 23 | #include "atom.h"
|
| 24 | #include "../sof-acpi-dev.h"
|
| 25 | #include "../sof-audio.h"
|
| 26 | #include "../../intel/common/soc-intel-quirks.h"
|
| 27 |
|
| 28 | static void atom_host_done(struct snd_sof_dev *sdev);
|
| 29 | static void atom_dsp_done(struct snd_sof_dev *sdev);
|
| 30 |
|
| 31 | |
| 32 | * Debug
|
| 33 |
|
| 34 |
|
| 35 | static void atom_get_registers(struct snd_sof_dev *sdev,
|
| 36 | struct sof_ipc_dsp_oops_xtensa *xoops,
|
| 37 | struct sof_ipc_panic_info *panic_info,
|
| 38 | u32 *stack, size_t stack_words)
|
| 39 | {
|
| 40 | u32 offset = sdev->dsp_oops_offset;
|
| 41 |
|
| 42 |
|
| 43 | sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops));
|
| 44 |
|
| 45 |
|
| 46 |
|
| 47 |
|
| 48 | if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) {
|
| 49 | dev_err(sdev->dev, "invalid header size 0x%x. FW oops is bogus\n",
|
| 50 | xoops->arch_hdr.totalsize);
|
| 51 | return;
|
| 52 | }
|
| 53 | offset += xoops->arch_hdr.totalsize;
|
| 54 | sof_mailbox_read(sdev, offset, panic_info, sizeof(*panic_info));
|
| 55 |
|
| 56 |
|
| 57 | offset += sizeof(*panic_info);
|
| 58 | sof_mailbox_read(sdev, offset, stack, stack_words * sizeof(u32));
|
| 59 | }
|
| 60 |
|
| 61 | void atom_dump(struct snd_sof_dev *sdev, u32 flags)
|
| 62 | {
|
| 63 | struct sof_ipc_dsp_oops_xtensa xoops;
|
| 64 | struct sof_ipc_panic_info panic_info;
|
| 65 | u32 stack[STACK_DUMP_SIZE];
|
| 66 | u64 status, panic, imrd, imrx;
|
| 67 |
|
| 68 |
|
| 69 | status = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCD);
|
| 70 | panic = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCX);
|
| 71 | atom_get_registers(sdev, &xoops, &panic_info, stack,
|
| 72 | STACK_DUMP_SIZE);
|
| 73 | sof_print_oops_and_stack(sdev, KERN_ERR, status, panic, &xoops,
|
| 74 | &panic_info, stack, STACK_DUMP_SIZE);
|
| 75 |
|
| 76 |
|
| 77 | imrx = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IMRX);
|
| 78 | imrd = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IMRD);
|
| 79 | dev_err(sdev->dev,
|
| 80 | "error: ipc host -> DSP: pending %s complete %s raw 0x%llx\n",
|
| 81 | str_yes_no(panic & SHIM_IPCX_BUSY),
|
| 82 | str_yes_no(panic & SHIM_IPCX_DONE), panic);
|
| 83 | dev_err(sdev->dev,
|
| 84 | "error: mask host: pending %s complete %s raw 0x%llx\n",
|
| 85 | str_yes_no(imrx & SHIM_IMRX_BUSY),
|
| 86 | str_yes_no(imrx & SHIM_IMRX_DONE), imrx);
|
| 87 | dev_err(sdev->dev,
|
| 88 | "error: ipc DSP -> host: pending %s complete %s raw 0x%llx\n",
|
| 89 | str_yes_no(status & SHIM_IPCD_BUSY),
|
| 90 | str_yes_no(status & SHIM_IPCD_DONE), status);
|
| 91 | dev_err(sdev->dev,
|
| 92 | "error: mask DSP: pending %s complete %s raw 0x%llx\n",
|
| 93 | str_yes_no(imrd & SHIM_IMRD_BUSY),
|
| 94 | str_yes_no(imrd & SHIM_IMRD_DONE), imrd);
|
| 95 |
|
| 96 | }
|
| 97 | EXPORT_SYMBOL_NS(atom_dump, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
|
| 98 |
|
| 99 | |
| 100 | * IPC Doorbell IRQ handler and thread.
|
| 101 |
|
| 102 |
|
| 103 | irqreturn_t atom_irq_handler(int irq, void *context)
|
| 104 | {
|
| 105 | struct snd_sof_dev *sdev = context;
|
| 106 | u64 ipcx, ipcd;
|
| 107 | int ret = IRQ_NONE;
|
| 108 |
|
| 109 | ipcx = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCX);
|
| 110 | ipcd = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCD);
|
| 111 |
|
| 112 | if (ipcx & SHIM_BYT_IPCX_DONE) {
|
| 113 |
|
| 114 |
|
| 115 | snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR,
|
| 116 | SHIM_IMRX,
|
| 117 | SHIM_IMRX_DONE,
|
| 118 | SHIM_IMRX_DONE);
|
| 119 | ret = IRQ_WAKE_THREAD;
|
| 120 | }
|
| 121 |
|
| 122 | if (ipcd & SHIM_BYT_IPCD_BUSY) {
|
| 123 |
|
| 124 |
|
| 125 | snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR,
|
| 126 | SHIM_IMRX,
|
| 127 | SHIM_IMRX_BUSY,
|
| 128 | SHIM_IMRX_BUSY);
|
| 129 | ret = IRQ_WAKE_THREAD;
|
| 130 | }
|
| 131 |
|
| 132 | return ret;
|
| 133 | }
|
| 134 | EXPORT_SYMBOL_NS(atom_irq_handler, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
|
| 135 |
|
| 136 | irqreturn_t atom_irq_thread(int irq, void *context)
|
| 137 | {
|
| 138 | struct snd_sof_dev *sdev = context;
|
| 139 | u64 ipcx, ipcd;
|
| 140 |
|
| 141 | ipcx = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCX);
|
| 142 | ipcd = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCD);
|
| 143 |
|
| 144 |
|
| 145 | if (ipcx & SHIM_BYT_IPCX_DONE) {
|
| 146 | |
| 147 | * handle immediate reply from DSP core. If the msg is
|
| 148 | * found, set done bit in cmd_done which is called at the
|
| 149 | * end of message processing function, else set it here
|
| 150 | * because the done bit can't be set in cmd_done function
|
| 151 | * which is triggered by msg
|
| 152 |
|
| 153 | guard(spinlock_irq)(&sdev->ipc_lock);
|
| 154 | snd_sof_ipc_process_reply(sdev, ipcx);
|
| 155 | atom_dsp_done(sdev);
|
| 156 | }
|
| 157 |
|
| 158 |
|
| 159 | if (ipcd & SHIM_BYT_IPCD_BUSY) {
|
| 160 |
|
| 161 |
|
| 162 | if ((ipcd & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) {
|
| 163 | snd_sof_dsp_panic(sdev, PANIC_OFFSET(ipcd) + MBOX_OFFSET,
|
| 164 | true);
|
| 165 | } else {
|
| 166 | snd_sof_ipc_msgs_rx(sdev);
|
| 167 | }
|
| 168 |
|
| 169 | atom_host_done(sdev);
|
| 170 | }
|
| 171 |
|
| 172 | return IRQ_HANDLED;
|
| 173 | }
|
| 174 | EXPORT_SYMBOL_NS(atom_irq_thread, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
|
| 175 |
|
| 176 | int atom_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
|
| 177 | {
|
| 178 |
|
| 179 | snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR, SHIM_IMRX,
|
| 180 | SHIM_IMRX_DONE, 0);
|
| 181 |
|
| 182 |
|
| 183 | sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
|
| 184 | msg->msg_size);
|
| 185 | snd_sof_dsp_write64(sdev, DSP_BAR, SHIM_IPCX, SHIM_BYT_IPCX_BUSY);
|
| 186 |
|
| 187 | return 0;
|
| 188 | }
|
| 189 | EXPORT_SYMBOL_NS(atom_send_msg, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
|
| 190 |
|
| 191 | int atom_get_mailbox_offset(struct snd_sof_dev *sdev)
|
| 192 | {
|
| 193 | return MBOX_OFFSET;
|
| 194 | }
|
| 195 | EXPORT_SYMBOL_NS(atom_get_mailbox_offset, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
|
| 196 |
|
| 197 | int atom_get_window_offset(struct snd_sof_dev *sdev, u32 id)
|
| 198 | {
|
| 199 | return MBOX_OFFSET;
|
| 200 | }
|
| 201 | EXPORT_SYMBOL_NS(atom_get_window_offset, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
|
| 202 |
|
| 203 | static void atom_host_done(struct snd_sof_dev *sdev)
|
| 204 | {
|
| 205 |
|
| 206 | snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR, SHIM_IPCD,
|
| 207 | SHIM_BYT_IPCD_BUSY |
|
| 208 | SHIM_BYT_IPCD_DONE,
|
| 209 | SHIM_BYT_IPCD_DONE);
|
| 210 |
|
| 211 |
|
| 212 | snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR, SHIM_IMRX,
|
| 213 | SHIM_IMRX_BUSY, 0);
|
| 214 | }
|
| 215 |
|
| 216 | static void atom_dsp_done(struct snd_sof_dev *sdev)
|
| 217 | {
|
| 218 |
|
| 219 | snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR, SHIM_IPCX,
|
| 220 | SHIM_BYT_IPCX_DONE, 0);
|
| 221 | }
|
| 222 |
|
| 223 | |
| 224 | * DSP control.
|
| 225 |
|
| 226 |
|
| 227 | int atom_run(struct snd_sof_dev *sdev)
|
| 228 | {
|
| 229 | int tries = 10;
|
| 230 |
|
| 231 |
|
| 232 | snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_CSR,
|
| 233 | SHIM_BYT_CSR_STALL, 0x0);
|
| 234 | while (tries--) {
|
| 235 | if (!(snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_CSR) &
|
| 236 | SHIM_BYT_CSR_PWAITMODE))
|
| 237 | break;
|
| 238 | msleep(100);
|
| 239 | }
|
| 240 | if (tries < 0)
|
| 241 | return -ENODEV;
|
| 242 |
|
| 243 |
|
| 244 | return 1;
|
| 245 | }
|
| 246 | EXPORT_SYMBOL_NS(atom_run, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
|
| 247 |
|
| 248 | int atom_reset(struct snd_sof_dev *sdev)
|
| 249 | {
|
| 250 |
|
| 251 | snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_CSR,
|
| 252 | SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL |
|
| 253 | SHIM_BYT_CSR_STALL,
|
| 254 | SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL |
|
| 255 | SHIM_BYT_CSR_STALL);
|
| 256 |
|
| 257 | usleep_range(10, 15);
|
| 258 |
|
| 259 |
|
| 260 | snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_CSR,
|
| 261 | SHIM_BYT_CSR_RST, 0);
|
| 262 |
|
| 263 | return 0;
|
| 264 | }
|
| 265 | EXPORT_SYMBOL_NS(atom_reset, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
|
| 266 |
|
| 267 | static const char *fixup_tplg_name(struct snd_sof_dev *sdev,
|
| 268 | const char *sof_tplg_filename,
|
| 269 | const char *ssp_str)
|
| 270 | {
|
| 271 | const char *tplg_filename = NULL;
|
| 272 | const char *split_ext;
|
| 273 | char *filename, *tmp;
|
| 274 |
|
| 275 | filename = kstrdup(sof_tplg_filename, GFP_KERNEL);
|
| 276 | if (!filename)
|
| 277 | return NULL;
|
| 278 |
|
| 279 |
|
| 280 | tmp = filename;
|
| 281 | split_ext = strsep(&tmp, ".");
|
| 282 | if (split_ext)
|
| 283 | tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL,
|
| 284 | "%s-%s.tplg",
|
| 285 | split_ext, ssp_str);
|
| 286 | kfree(filename);
|
| 287 |
|
| 288 | return tplg_filename;
|
| 289 | }
|
| 290 |
|
| 291 | struct snd_soc_acpi_mach *atom_machine_select(struct snd_sof_dev *sdev)
|
| 292 | {
|
| 293 | struct snd_sof_pdata *sof_pdata = sdev->pdata;
|
| 294 | const struct sof_dev_desc *desc = sof_pdata->desc;
|
| 295 | struct snd_soc_acpi_mach *mach;
|
| 296 | struct platform_device *pdev;
|
| 297 | const char *tplg_filename;
|
| 298 |
|
| 299 | mach = snd_soc_acpi_find_machine(desc->machines);
|
| 300 | if (!mach) {
|
| 301 | dev_warn(sdev->dev, "warning: No matching ASoC machine driver found\n");
|
| 302 | return NULL;
|
| 303 | }
|
| 304 |
|
| 305 | pdev = to_platform_device(sdev->dev);
|
| 306 | if (soc_intel_is_byt_cr(pdev)) {
|
| 307 | dev_dbg(sdev->dev,
|
| 308 | "BYT-CR detected, SSP0 used instead of SSP2\n");
|
| 309 |
|
| 310 | tplg_filename = fixup_tplg_name(sdev,
|
| 311 | mach->sof_tplg_filename,
|
| 312 | "ssp0");
|
| 313 | } else {
|
| 314 | tplg_filename = mach->sof_tplg_filename;
|
| 315 | }
|
| 316 |
|
| 317 | if (!tplg_filename) {
|
| 318 | dev_dbg(sdev->dev,
|
| 319 | "error: no topology filename\n");
|
| 320 | return NULL;
|
| 321 | }
|
| 322 |
|
| 323 | sof_pdata->tplg_filename = tplg_filename;
|
| 324 | mach->mach_params.acpi_ipc_irq_index = desc->irqindex_host_ipc;
|
| 325 |
|
| 326 | return mach;
|
| 327 | }
|
| 328 | EXPORT_SYMBOL_NS(atom_machine_select, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
|
| 329 |
|
| 330 |
|
| 331 | struct snd_soc_dai_driver atom_dai[] = {
|
| 332 | {
|
| 333 | .name = "ssp0-port",
|
| 334 | .playback = {
|
| 335 | .channels_min = 1,
|
| 336 | .channels_max = 8,
|
| 337 | },
|
| 338 | .capture = {
|
| 339 | .channels_min = 1,
|
| 340 | .channels_max = 8,
|
| 341 | },
|
| 342 | },
|
| 343 | {
|
| 344 | .name = "ssp1-port",
|
| 345 | .playback = {
|
| 346 | .channels_min = 1,
|
| 347 | .channels_max = 8,
|
| 348 | },
|
| 349 | .capture = {
|
| 350 | .channels_min = 1,
|
| 351 | .channels_max = 8,
|
| 352 | },
|
| 353 | },
|
| 354 | {
|
| 355 | .name = "ssp2-port",
|
| 356 | .playback = {
|
| 357 | .channels_min = 1,
|
| 358 | .channels_max = 8,
|
| 359 | },
|
| 360 | .capture = {
|
| 361 | .channels_min = 1,
|
| 362 | .channels_max = 8,
|
| 363 | }
|
| 364 | },
|
| 365 | {
|
| 366 | .name = "ssp3-port",
|
| 367 | .playback = {
|
| 368 | .channels_min = 1,
|
| 369 | .channels_max = 8,
|
| 370 | },
|
| 371 | .capture = {
|
| 372 | .channels_min = 1,
|
| 373 | .channels_max = 8,
|
| 374 | },
|
| 375 | },
|
| 376 | {
|
| 377 | .name = "ssp4-port",
|
| 378 | .playback = {
|
| 379 | .channels_min = 1,
|
| 380 | .channels_max = 8,
|
| 381 | },
|
| 382 | .capture = {
|
| 383 | .channels_min = 1,
|
| 384 | .channels_max = 8,
|
| 385 | },
|
| 386 | },
|
| 387 | {
|
| 388 | .name = "ssp5-port",
|
| 389 | .playback = {
|
| 390 | .channels_min = 1,
|
| 391 | .channels_max = 8,
|
| 392 | },
|
| 393 | .capture = {
|
| 394 | .channels_min = 1,
|
| 395 | .channels_max = 8,
|
| 396 | },
|
| 397 | },
|
| 398 | };
|
| 399 | EXPORT_SYMBOL_NS(atom_dai, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
|
| 400 |
|
| 401 | void atom_set_mach_params(struct snd_soc_acpi_mach *mach,
|
| 402 | struct snd_sof_dev *sdev)
|
| 403 | {
|
| 404 | struct snd_sof_pdata *pdata = sdev->pdata;
|
| 405 | const struct sof_dev_desc *desc = pdata->desc;
|
| 406 | struct snd_soc_acpi_mach_params *mach_params;
|
| 407 |
|
| 408 | mach_params = &mach->mach_params;
|
| 409 | mach_params->platform = dev_name(sdev->dev);
|
| 410 | mach_params->num_dai_drivers = desc->ops->num_drv;
|
| 411 | mach_params->dai_drivers = desc->ops->drv;
|
| 412 | }
|
| 413 | EXPORT_SYMBOL_NS(atom_set_mach_params, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
|
| 414 |
|
| 415 | MODULE_LICENSE("Dual BSD/GPL");
|
| 416 | MODULE_DESCRIPTION("SOF support for Atom platforms");
|