|
10 | 10 | #include <linux/soundwire/sdw_registers.h>
|
11 | 11 | #include <linux/soundwire/sdw.h>
|
12 | 12 | #include <linux/soundwire/sdw_intel.h>
|
| 13 | +#include <sound/pcm_params.h> |
13 | 14 | #include <sound/hda-mlink.h>
|
14 | 15 | #include "cadence_master.h"
|
15 | 16 | #include "bus.h"
|
@@ -191,10 +192,292 @@ static bool intel_check_cmdsync_unlocked(struct sdw_intel *sdw)
|
191 | 192 | return hdac_bus_eml_sdw_check_cmdsync_unlocked(sdw->link_res->hbus);
|
192 | 193 | }
|
193 | 194 |
|
| 195 | +/* DAI callbacks */ |
| 196 | +static int intel_params_stream(struct sdw_intel *sdw, |
| 197 | + struct snd_pcm_substream *substream, |
| 198 | + struct snd_soc_dai *dai, |
| 199 | + struct snd_pcm_hw_params *hw_params, |
| 200 | + int link_id, int alh_stream_id) |
| 201 | +{ |
| 202 | + struct sdw_intel_link_res *res = sdw->link_res; |
| 203 | + struct sdw_intel_stream_params_data params_data; |
| 204 | + |
| 205 | + params_data.substream = substream; |
| 206 | + params_data.dai = dai; |
| 207 | + params_data.hw_params = hw_params; |
| 208 | + params_data.link_id = link_id; |
| 209 | + params_data.alh_stream_id = alh_stream_id; |
| 210 | + |
| 211 | + if (res->ops && res->ops->params_stream && res->dev) |
| 212 | + return res->ops->params_stream(res->dev, |
| 213 | + ¶ms_data); |
| 214 | + return -EIO; |
| 215 | +} |
| 216 | + |
| 217 | +static int intel_free_stream(struct sdw_intel *sdw, |
| 218 | + struct snd_pcm_substream *substream, |
| 219 | + struct snd_soc_dai *dai, |
| 220 | + int link_id) |
| 221 | + |
| 222 | +{ |
| 223 | + struct sdw_intel_link_res *res = sdw->link_res; |
| 224 | + struct sdw_intel_stream_free_data free_data; |
| 225 | + |
| 226 | + free_data.substream = substream; |
| 227 | + free_data.dai = dai; |
| 228 | + free_data.link_id = link_id; |
| 229 | + |
| 230 | + if (res->ops && res->ops->free_stream && res->dev) |
| 231 | + return res->ops->free_stream(res->dev, |
| 232 | + &free_data); |
| 233 | + |
| 234 | + return 0; |
| 235 | +} |
| 236 | + |
194 | 237 | /*
|
195 | 238 | * DAI operations
|
196 | 239 | */
|
| 240 | +static int intel_hw_params(struct snd_pcm_substream *substream, |
| 241 | + struct snd_pcm_hw_params *params, |
| 242 | + struct snd_soc_dai *dai) |
| 243 | +{ |
| 244 | + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); |
| 245 | + struct sdw_intel *sdw = cdns_to_intel(cdns); |
| 246 | + struct sdw_cdns_dai_runtime *dai_runtime; |
| 247 | + struct sdw_cdns_pdi *pdi; |
| 248 | + struct sdw_stream_config sconfig; |
| 249 | + struct sdw_port_config *pconfig; |
| 250 | + int ch, dir; |
| 251 | + int ret; |
| 252 | + |
| 253 | + dai_runtime = cdns->dai_runtime_array[dai->id]; |
| 254 | + if (!dai_runtime) |
| 255 | + return -EIO; |
| 256 | + |
| 257 | + ch = params_channels(params); |
| 258 | + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
| 259 | + dir = SDW_DATA_DIR_RX; |
| 260 | + else |
| 261 | + dir = SDW_DATA_DIR_TX; |
| 262 | + |
| 263 | + pdi = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, ch, dir, dai->id); |
| 264 | + |
| 265 | + if (!pdi) { |
| 266 | + ret = -EINVAL; |
| 267 | + goto error; |
| 268 | + } |
| 269 | + |
| 270 | + /* the SHIM will be configured in the callback functions */ |
| 271 | + |
| 272 | + sdw_cdns_config_stream(cdns, ch, dir, pdi); |
| 273 | + |
| 274 | + /* store pdi and state, may be needed in prepare step */ |
| 275 | + dai_runtime->paused = false; |
| 276 | + dai_runtime->suspended = false; |
| 277 | + dai_runtime->pdi = pdi; |
| 278 | + |
| 279 | + /* Inform DSP about PDI stream number */ |
| 280 | + ret = intel_params_stream(sdw, substream, dai, params, |
| 281 | + sdw->instance, |
| 282 | + pdi->intel_alh_id); |
| 283 | + if (ret) |
| 284 | + goto error; |
| 285 | + |
| 286 | + sconfig.direction = dir; |
| 287 | + sconfig.ch_count = ch; |
| 288 | + sconfig.frame_rate = params_rate(params); |
| 289 | + sconfig.type = dai_runtime->stream_type; |
| 290 | + |
| 291 | + sconfig.bps = snd_pcm_format_width(params_format(params)); |
| 292 | + |
| 293 | + /* Port configuration */ |
| 294 | + pconfig = kzalloc(sizeof(*pconfig), GFP_KERNEL); |
| 295 | + if (!pconfig) { |
| 296 | + ret = -ENOMEM; |
| 297 | + goto error; |
| 298 | + } |
| 299 | + |
| 300 | + pconfig->num = pdi->num; |
| 301 | + pconfig->ch_mask = (1 << ch) - 1; |
| 302 | + |
| 303 | + ret = sdw_stream_add_master(&cdns->bus, &sconfig, |
| 304 | + pconfig, 1, dai_runtime->stream); |
| 305 | + if (ret) |
| 306 | + dev_err(cdns->dev, "add master to stream failed:%d\n", ret); |
| 307 | + |
| 308 | + kfree(pconfig); |
| 309 | +error: |
| 310 | + return ret; |
| 311 | +} |
| 312 | + |
| 313 | +static int intel_prepare(struct snd_pcm_substream *substream, |
| 314 | + struct snd_soc_dai *dai) |
| 315 | +{ |
| 316 | + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); |
| 317 | + struct sdw_intel *sdw = cdns_to_intel(cdns); |
| 318 | + struct sdw_cdns_dai_runtime *dai_runtime; |
| 319 | + int ch, dir; |
| 320 | + int ret = 0; |
| 321 | + |
| 322 | + dai_runtime = cdns->dai_runtime_array[dai->id]; |
| 323 | + if (!dai_runtime) { |
| 324 | + dev_err(dai->dev, "failed to get dai runtime in %s\n", |
| 325 | + __func__); |
| 326 | + return -EIO; |
| 327 | + } |
| 328 | + |
| 329 | + if (dai_runtime->suspended) { |
| 330 | + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); |
| 331 | + struct snd_pcm_hw_params *hw_params; |
| 332 | + |
| 333 | + hw_params = &rtd->dpcm[substream->stream].hw_params; |
| 334 | + |
| 335 | + dai_runtime->suspended = false; |
| 336 | + |
| 337 | + /* |
| 338 | + * .prepare() is called after system resume, where we |
| 339 | + * need to reinitialize the SHIM/ALH/Cadence IP. |
| 340 | + * .prepare() is also called to deal with underflows, |
| 341 | + * but in those cases we cannot touch ALH/SHIM |
| 342 | + * registers |
| 343 | + */ |
| 344 | + |
| 345 | + /* configure stream */ |
| 346 | + ch = params_channels(hw_params); |
| 347 | + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) |
| 348 | + dir = SDW_DATA_DIR_RX; |
| 349 | + else |
| 350 | + dir = SDW_DATA_DIR_TX; |
| 351 | + |
| 352 | + /* the SHIM will be configured in the callback functions */ |
| 353 | + |
| 354 | + sdw_cdns_config_stream(cdns, ch, dir, dai_runtime->pdi); |
| 355 | + |
| 356 | + /* Inform DSP about PDI stream number */ |
| 357 | + ret = intel_params_stream(sdw, substream, dai, |
| 358 | + hw_params, |
| 359 | + sdw->instance, |
| 360 | + dai_runtime->pdi->intel_alh_id); |
| 361 | + } |
| 362 | + |
| 363 | + return ret; |
| 364 | +} |
| 365 | + |
| 366 | +static int |
| 367 | +intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) |
| 368 | +{ |
| 369 | + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); |
| 370 | + struct sdw_intel *sdw = cdns_to_intel(cdns); |
| 371 | + struct sdw_cdns_dai_runtime *dai_runtime; |
| 372 | + int ret; |
| 373 | + |
| 374 | + dai_runtime = cdns->dai_runtime_array[dai->id]; |
| 375 | + if (!dai_runtime) |
| 376 | + return -EIO; |
| 377 | + |
| 378 | + /* |
| 379 | + * The sdw stream state will transition to RELEASED when stream-> |
| 380 | + * master_list is empty. So the stream state will transition to |
| 381 | + * DEPREPARED for the first cpu-dai and to RELEASED for the last |
| 382 | + * cpu-dai. |
| 383 | + */ |
| 384 | + ret = sdw_stream_remove_master(&cdns->bus, dai_runtime->stream); |
| 385 | + if (ret < 0) { |
| 386 | + dev_err(dai->dev, "remove master from stream %s failed: %d\n", |
| 387 | + dai_runtime->stream->name, ret); |
| 388 | + return ret; |
| 389 | + } |
| 390 | + |
| 391 | + ret = intel_free_stream(sdw, substream, dai, sdw->instance); |
| 392 | + if (ret < 0) { |
| 393 | + dev_err(dai->dev, "intel_free_stream: failed %d\n", ret); |
| 394 | + return ret; |
| 395 | + } |
| 396 | + |
| 397 | + dai_runtime->pdi = NULL; |
| 398 | + |
| 399 | + return 0; |
| 400 | +} |
| 401 | + |
| 402 | +static int intel_pcm_set_sdw_stream(struct snd_soc_dai *dai, |
| 403 | + void *stream, int direction) |
| 404 | +{ |
| 405 | + return cdns_set_sdw_stream(dai, stream, direction); |
| 406 | +} |
| 407 | + |
| 408 | +static void *intel_get_sdw_stream(struct snd_soc_dai *dai, |
| 409 | + int direction) |
| 410 | +{ |
| 411 | + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); |
| 412 | + struct sdw_cdns_dai_runtime *dai_runtime; |
| 413 | + |
| 414 | + dai_runtime = cdns->dai_runtime_array[dai->id]; |
| 415 | + if (!dai_runtime) |
| 416 | + return ERR_PTR(-EINVAL); |
| 417 | + |
| 418 | + return dai_runtime->stream; |
| 419 | +} |
| 420 | + |
| 421 | +static int intel_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) |
| 422 | +{ |
| 423 | + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); |
| 424 | + struct sdw_intel *sdw = cdns_to_intel(cdns); |
| 425 | + struct sdw_intel_link_res *res = sdw->link_res; |
| 426 | + struct sdw_cdns_dai_runtime *dai_runtime; |
| 427 | + int ret = 0; |
| 428 | + |
| 429 | + /* |
| 430 | + * The .trigger callback is used to program HDaudio DMA and send required IPC to audio |
| 431 | + * firmware. |
| 432 | + */ |
| 433 | + if (res->ops && res->ops->trigger) { |
| 434 | + ret = res->ops->trigger(substream, cmd, dai); |
| 435 | + if (ret < 0) |
| 436 | + return ret; |
| 437 | + } |
| 438 | + |
| 439 | + dai_runtime = cdns->dai_runtime_array[dai->id]; |
| 440 | + if (!dai_runtime) { |
| 441 | + dev_err(dai->dev, "failed to get dai runtime in %s\n", |
| 442 | + __func__); |
| 443 | + return -EIO; |
| 444 | + } |
| 445 | + |
| 446 | + switch (cmd) { |
| 447 | + case SNDRV_PCM_TRIGGER_SUSPEND: |
| 448 | + |
| 449 | + /* |
| 450 | + * The .prepare callback is used to deal with xruns and resume operations. |
| 451 | + * In the case of xruns, the DMAs and SHIM registers cannot be touched, |
| 452 | + * but for resume operations the DMAs and SHIM registers need to be initialized. |
| 453 | + * the .trigger callback is used to track the suspend case only. |
| 454 | + */ |
| 455 | + |
| 456 | + dai_runtime->suspended = true; |
| 457 | + |
| 458 | + break; |
| 459 | + |
| 460 | + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
| 461 | + dai_runtime->paused = true; |
| 462 | + break; |
| 463 | + case SNDRV_PCM_TRIGGER_STOP: |
| 464 | + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
| 465 | + dai_runtime->paused = false; |
| 466 | + break; |
| 467 | + default: |
| 468 | + break; |
| 469 | + } |
| 470 | + |
| 471 | + return ret; |
| 472 | +} |
| 473 | + |
197 | 474 | static const struct snd_soc_dai_ops intel_pcm_dai_ops = {
|
| 475 | + .hw_params = intel_hw_params, |
| 476 | + .prepare = intel_prepare, |
| 477 | + .hw_free = intel_hw_free, |
| 478 | + .trigger = intel_trigger, |
| 479 | + .set_stream = intel_pcm_set_sdw_stream, |
| 480 | + .get_stream = intel_get_sdw_stream, |
198 | 481 | };
|
199 | 482 |
|
200 | 483 | static const struct snd_soc_component_driver dai_component = {
|
|
0 commit comments