Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 | /*
* intel_sst_interface.c - Intel SST Driver for audio engine
*
* Copyright (C) 2008-10 Intel Corp
* Authors: Vinod Koul <vinod.koul@intel.com>
* Harsha Priya <priya.harsha@intel.com>
* Dharageswari R <dharageswari.r@intel.com)
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* This driver exposes the audio engine functionalities to the ALSA
* and middleware.
* Upper layer interfaces (MAD driver, MMF) to SST driver
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/firmware.h>
#include <linux/pm_runtime.h>
#include <linux/export.h>
#include "intel_sst.h"
#include "intel_sst_ioctl.h"
#include "intel_sst_fw_ipc.h"
#include "intel_sst_common.h"
/*
* sst_download_fw - download the audio firmware to DSP
*
* This function is called when the FW needs to be downloaded to SST DSP engine
*/
int sst_download_fw(void)
{
int retval;
const struct firmware *fw_sst;
char name[20];
if (sst_drv_ctx->sst_state != SST_UN_INIT)
return -EPERM;
/* Reload firmware is not needed for MRST */
if ( (sst_drv_ctx->pci_id == SST_MRST_PCI_ID) && sst_drv_ctx->fw_downloaded) {
pr_debug("FW already downloaded, skip for MRST platform\n");
sst_drv_ctx->sst_state = SST_FW_RUNNING;
return 0;
}
snprintf(name, sizeof(name), "%s%04x%s", "fw_sst_",
sst_drv_ctx->pci_id, ".bin");
pr_debug("Downloading %s FW now...\n", name);
retval = request_firmware(&fw_sst, name, &sst_drv_ctx->pci->dev);
if (retval) {
pr_err("request fw failed %d\n", retval);
return retval;
}
sst_drv_ctx->alloc_block[0].sst_id = FW_DWNL_ID;
sst_drv_ctx->alloc_block[0].ops_block.condition = false;
retval = sst_load_fw(fw_sst, NULL);
if (retval)
goto end_restore;
retval = sst_wait_timeout(sst_drv_ctx, &sst_drv_ctx->alloc_block[0]);
if (retval)
pr_err("fw download failed %d\n" , retval);
else
sst_drv_ctx->fw_downloaded = 1;
end_restore:
release_firmware(fw_sst);
sst_drv_ctx->alloc_block[0].sst_id = BLOCK_UNINIT;
return retval;
}
/*
* sst_stalled - this function checks if the lpe is in stalled state
*/
int sst_stalled(void)
{
int retry = 1000;
int retval = -1;
while (retry) {
if (!sst_drv_ctx->lpe_stalled)
return 0;
/*wait for time and re-check*/
msleep(1);
retry--;
}
pr_debug("in Stalled State\n");
return retval;
}
void free_stream_context(unsigned int str_id)
{
struct stream_info *stream;
if (!sst_validate_strid(str_id)) {
/* str_id is valid, so stream is alloacted */
stream = &sst_drv_ctx->streams[str_id];
if (sst_free_stream(str_id))
sst_clean_stream(&sst_drv_ctx->streams[str_id]);
if (stream->ops == STREAM_OPS_PLAYBACK ||
stream->ops == STREAM_OPS_PLAYBACK_DRM) {
sst_drv_ctx->pb_streams--;
if (sst_drv_ctx->pci_id == SST_MFLD_PCI_ID)
sst_drv_ctx->scard_ops->power_down_pmic_pb(
stream->device);
else {
if (sst_drv_ctx->pb_streams == 0)
sst_drv_ctx->scard_ops->
power_down_pmic_pb(stream->device);
}
} else if (stream->ops == STREAM_OPS_CAPTURE) {
sst_drv_ctx->cp_streams--;
if (sst_drv_ctx->cp_streams == 0)
sst_drv_ctx->scard_ops->power_down_pmic_cp(
stream->device);
}
if (sst_drv_ctx->pb_streams == 0
&& sst_drv_ctx->cp_streams == 0)
sst_drv_ctx->scard_ops->power_down_pmic();
}
}
/*
* sst_get_stream_allocated - this function gets a stream allocated with
* the given params
*
* @str_param : stream params
* @lib_dnld : pointer to pointer of lib downlaod struct
*
* This creates new stream id for a stream, in case lib is to be downloaded to
* DSP, it downloads that
*/
int sst_get_stream_allocated(struct snd_sst_params *str_param,
struct snd_sst_lib_download **lib_dnld)
{
int retval, str_id;
struct stream_info *str_info;
retval = sst_alloc_stream((char *) &str_param->sparams, str_param->ops,
str_param->codec, str_param->device_type);
if (retval < 0) {
pr_err("sst_alloc_stream failed %d\n", retval);
return retval;
}
pr_debug("Stream allocated %d\n", retval);
str_id = retval;
str_info = &sst_drv_ctx->streams[str_id];
/* Block the call for reply */
retval = sst_wait_interruptible_timeout(sst_drv_ctx,
&str_info->ctrl_blk, SST_BLOCK_TIMEOUT);
if ((retval != 0) || (str_info->ctrl_blk.ret_code != 0)) {
pr_debug("FW alloc failed retval %d, ret_code %d\n",
retval, str_info->ctrl_blk.ret_code);
str_id = -str_info->ctrl_blk.ret_code; /*return error*/
*lib_dnld = str_info->ctrl_blk.data;
sst_clean_stream(str_info);
} else
pr_debug("FW Stream allocated success\n");
return str_id; /*will ret either error (in above if) or correct str id*/
}
/*
* sst_get_sfreq - this function returns the frequency of the stream
*
* @str_param : stream params
*/
static int sst_get_sfreq(struct snd_sst_params *str_param)
{
switch (str_param->codec) {
case SST_CODEC_TYPE_PCM:
return 48000; /*str_param->sparams.uc.pcm_params.sfreq;*/
case SST_CODEC_TYPE_MP3:
return str_param->sparams.uc.mp3_params.sfreq;
case SST_CODEC_TYPE_AAC:
return str_param->sparams.uc.aac_params.sfreq;
case SST_CODEC_TYPE_WMA9:
return str_param->sparams.uc.wma_params.sfreq;
default:
return 0;
}
}
/*
* sst_get_stream - this function prepares for stream allocation
*
* @str_param : stream param
*/
int sst_get_stream(struct snd_sst_params *str_param)
{
int i, retval;
struct stream_info *str_info;
struct snd_sst_lib_download *lib_dnld;
/* stream is not allocated, we are allocating */
retval = sst_get_stream_allocated(str_param, &lib_dnld);
if (retval == -(SST_LIB_ERR_LIB_DNLD_REQUIRED)) {
/* codec download is required */
struct snd_sst_alloc_response *response;
pr_debug("Codec is required.... trying that\n");
if (lib_dnld == NULL) {
pr_err("lib download null!!! abort\n");
return -EIO;
}
i = sst_get_block_stream(sst_drv_ctx);
response = sst_drv_ctx->alloc_block[i].ops_block.data;
pr_debug("alloc block allocated = %d\n", i);
if (i < 0) {
kfree(lib_dnld);
return -ENOMEM;
}
retval = sst_load_library(lib_dnld, str_param->ops);
kfree(lib_dnld);
sst_drv_ctx->alloc_block[i].sst_id = BLOCK_UNINIT;
if (!retval) {
pr_debug("codec was downloaded successfully\n");
retval = sst_get_stream_allocated(str_param, &lib_dnld);
if (retval <= 0)
goto err;
pr_debug("Alloc done stream id %d\n", retval);
} else {
pr_debug("codec download failed\n");
retval = -EIO;
goto err;
}
} else if (retval <= 0)
goto err;
/*else
set_port_params(str_param, str_param->ops);*/
/* store sampling freq */
str_info = &sst_drv_ctx->streams[retval];
str_info->sfreq = sst_get_sfreq(str_param);
/* power on the analog, if reqd */
if (str_param->ops == STREAM_OPS_PLAYBACK ||
str_param->ops == STREAM_OPS_PLAYBACK_DRM) {
if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID)
sst_drv_ctx->scard_ops->power_up_pmic_pb(
sst_drv_ctx->pmic_port_instance);
else
sst_drv_ctx->scard_ops->power_up_pmic_pb(
str_info->device);
/*Only if the playback is MP3 - Send a message*/
sst_drv_ctx->pb_streams++;
} else if (str_param->ops == STREAM_OPS_CAPTURE) {
sst_drv_ctx->scard_ops->power_up_pmic_cp(
sst_drv_ctx->pmic_port_instance);
/*Send a messageif not sent already*/
sst_drv_ctx->cp_streams++;
}
err:
return retval;
}
void sst_process_mad_ops(struct work_struct *work)
{
struct mad_ops_wq *mad_ops =
container_of(work, struct mad_ops_wq, wq);
int retval = 0;
switch (mad_ops->control_op) {
case SST_SND_PAUSE:
retval = sst_pause_stream(mad_ops->stream_id);
break;
case SST_SND_RESUME:
retval = sst_resume_stream(mad_ops->stream_id);
break;
case SST_SND_DROP:
retval = sst_drop_stream(mad_ops->stream_id);
break;
case SST_SND_START:
pr_debug("SST Debug: start stream\n");
retval = sst_start_stream(mad_ops->stream_id);
break;
case SST_SND_STREAM_PROCESS:
pr_debug("play/capt frames...\n");
break;
default:
pr_err(" wrong control_ops reported\n");
}
return;
}
void send_intial_rx_timeslot(void)
{
if (sst_drv_ctx->pci_id == SST_MRST_PCI_ID &&
sst_drv_ctx->rx_time_slot_status != RX_TIMESLOT_UNINIT
&& sst_drv_ctx->pmic_vendor != SND_NC)
sst_enable_rx_timeslot(sst_drv_ctx->rx_time_slot_status);
}
/*
* sst_open_pcm_stream - Open PCM interface
*
* @str_param: parameters of pcm stream
*
* This function is called by MID sound card driver to open
* a new pcm interface
*/
int sst_open_pcm_stream(struct snd_sst_params *str_param)
{
struct stream_info *str_info;
int retval;
pm_runtime_get_sync(&sst_drv_ctx->pci->dev);
if (sst_drv_ctx->sst_state == SST_SUSPENDED) {
/* LPE is suspended, resume it before proceeding*/
pr_debug("Resuming from Suspended state\n");
retval = intel_sst_resume(sst_drv_ctx->pci);
if (retval) {
pr_err("Resume Failed = %#x, abort\n", retval);
pm_runtime_put(&sst_drv_ctx->pci->dev);
return retval;
}
}
if (sst_drv_ctx->sst_state == SST_UN_INIT) {
/* FW is not downloaded */
pr_debug("DSP Downloading FW now...\n");
retval = sst_download_fw();
if (retval) {
pr_err("FW download fail %x, abort\n", retval);
pm_runtime_put(&sst_drv_ctx->pci->dev);
return retval;
}
send_intial_rx_timeslot();
}
if (!str_param) {
pm_runtime_put(&sst_drv_ctx->pci->dev);
return -EINVAL;
}
retval = sst_get_stream(str_param);
if (retval > 0) {
sst_drv_ctx->stream_cnt++;
str_info = &sst_drv_ctx->streams[retval];
str_info->src = MAD_DRV;
} else
pm_runtime_put(&sst_drv_ctx->pci->dev);
return retval;
}
/*
* sst_close_pcm_stream - Close PCM interface
*
* @str_id: stream id to be closed
*
* This function is called by MID sound card driver to close
* an existing pcm interface
*/
int sst_close_pcm_stream(unsigned int str_id)
{
struct stream_info *stream;
pr_debug("sst: stream free called\n");
if (sst_validate_strid(str_id))
return -EINVAL;
stream = &sst_drv_ctx->streams[str_id];
free_stream_context(str_id);
stream->pcm_substream = NULL;
stream->status = STREAM_UN_INIT;
stream->period_elapsed = NULL;
sst_drv_ctx->stream_cnt--;
pr_debug("sst: will call runtime put now\n");
pm_runtime_put(&sst_drv_ctx->pci->dev);
return 0;
}
/*
* sst_device_control - Set Control params
*
* @cmd: control cmd to be set
* @arg: command argument
*
* This function is called by MID sound card driver to set
* SST/Sound card controls for an opened stream.
* This is registered with MID driver
*/
int sst_device_control(int cmd, void *arg)
{
int retval = 0, str_id = 0;
switch (cmd) {
case SST_SND_PAUSE:
case SST_SND_RESUME:
case SST_SND_DROP:
case SST_SND_START:
sst_drv_ctx->mad_ops.control_op = cmd;
sst_drv_ctx->mad_ops.stream_id = *(int *)arg;
queue_work(sst_drv_ctx->mad_wq, &sst_drv_ctx->mad_ops.wq);
break;
case SST_SND_STREAM_INIT: {
struct pcm_stream_info *str_info;
struct stream_info *stream;
pr_debug("stream init called\n");
str_info = (struct pcm_stream_info *)arg;
str_id = str_info->str_id;
retval = sst_validate_strid(str_id);
if (retval)
break;
stream = &sst_drv_ctx->streams[str_id];
pr_debug("setting the period ptrs\n");
stream->pcm_substream = str_info->mad_substream;
stream->period_elapsed = str_info->period_elapsed;
stream->sfreq = str_info->sfreq;
stream->prev = stream->status;
stream->status = STREAM_INIT;
break;
}
case SST_SND_BUFFER_POINTER: {
struct pcm_stream_info *stream_info;
struct snd_sst_tstamp fw_tstamp = {0,};
struct stream_info *stream;
stream_info = (struct pcm_stream_info *)arg;
str_id = stream_info->str_id;
retval = sst_validate_strid(str_id);
if (retval)
break;
stream = &sst_drv_ctx->streams[str_id];
if (!stream->pcm_substream)
break;
memcpy_fromio(&fw_tstamp,
((void *)(sst_drv_ctx->mailbox + SST_TIME_STAMP)
+(str_id * sizeof(fw_tstamp))),
sizeof(fw_tstamp));
pr_debug("Pointer Query on strid = %d ops %d\n",
str_id, stream->ops);
if (stream->ops == STREAM_OPS_PLAYBACK)
stream_info->buffer_ptr = fw_tstamp.samples_rendered;
else
stream_info->buffer_ptr = fw_tstamp.samples_processed;
pr_debug("Samples rendered = %llu, buffer ptr %llu\n",
fw_tstamp.samples_rendered, stream_info->buffer_ptr);
break;
}
case SST_ENABLE_RX_TIME_SLOT: {
int status = *(int *)arg;
sst_drv_ctx->rx_time_slot_status = status ;
sst_enable_rx_timeslot(status);
break;
}
default:
/* Illegal case */
pr_warn("illegal req\n");
return -EINVAL;
}
return retval;
}
struct intel_sst_pcm_control pcm_ops = {
.open = sst_open_pcm_stream,
.device_control = sst_device_control,
.close = sst_close_pcm_stream,
};
struct intel_sst_card_ops sst_pmic_ops = {
.pcm_control = &pcm_ops,
};
/*
* register_sst_card - function for sound card to register
*
* @card: pointer to structure of operations
*
* This function is called card driver loads and is ready for registration
*/
int register_sst_card(struct intel_sst_card_ops *card)
{
if (!sst_drv_ctx) {
pr_err("No SST driver register card reject\n");
return -ENODEV;
}
if (!card || !card->module_name) {
pr_err("Null Pointer Passed\n");
return -EINVAL;
}
if (sst_drv_ctx->pmic_state == SND_MAD_UN_INIT) {
/* register this driver */
if ((strncmp(SST_CARD_NAMES, card->module_name,
strlen(SST_CARD_NAMES))) == 0) {
sst_drv_ctx->pmic_vendor = card->vendor_id;
sst_drv_ctx->scard_ops = card->scard_ops;
sst_pmic_ops.module_name = card->module_name;
sst_drv_ctx->pmic_state = SND_MAD_INIT_DONE;
sst_drv_ctx->rx_time_slot_status = 0; /*default AMIC*/
card->pcm_control = sst_pmic_ops.pcm_control;
return 0;
} else {
pr_err("strcmp fail %s\n", card->module_name);
return -EINVAL;
}
} else {
/* already registered a driver */
pr_err("Repeat for registration..denied\n");
return -EBADRQC;
}
/* The ASoC code doesn't set scard_ops */
if (sst_drv_ctx->scard_ops)
sst_drv_ctx->scard_ops->card_status = SND_CARD_UN_INIT;
return 0;
}
EXPORT_SYMBOL_GPL(register_sst_card);
/*
* unregister_sst_card- function for sound card to un-register
*
* @card: pointer to structure of operations
*
* This function is called when card driver unloads
*/
void unregister_sst_card(struct intel_sst_card_ops *card)
{
if (sst_pmic_ops.pcm_control == card->pcm_control) {
/* unreg */
sst_pmic_ops.module_name = "";
sst_drv_ctx->pmic_state = SND_MAD_UN_INIT;
pr_debug("Unregistered %s\n", card->module_name);
}
return;
}
EXPORT_SYMBOL_GPL(unregister_sst_card);
|