kernel-ark/drivers/staging/intel_sst/intel_sst_drv_interface.c
Vinod Koul 79a35ad573 Staging: sst: Construct fw string name runtime
The firmware name for each platform is appended by PCI id of device.
This patch makes use of pci id to construct the string rather than
hardcode the string.

Signed-off-by: Vinod Koul <vinod.koul@intel.com>
Signed-off-by: Alan Cox <alan@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2010-11-29 11:13:55 -08:00

545 lines
14 KiB
C

/*
* 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 "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;
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);
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 (stream->ops == STREAM_OPS_PLAYBACK ||
stream->ops == STREAM_OPS_PLAYBACK_DRM) {
sst_drv_ctx->pb_streams--;
if (sst_drv_ctx->pb_streams == 0)
sst_drv_ctx->scard_ops->power_down_pmic_pb();
} 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();
}
if (sst_drv_ctx->pb_streams == 0
&& sst_drv_ctx->cp_streams == 0)
sst_drv_ctx->scard_ops->power_down_pmic();
if (sst_free_stream(str_id))
sst_clean_stream(&sst_drv_ctx->streams[str_id]);
}
}
/*
* 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 proceding*/
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;
sst_drv_ctx->scard_ops->card_status = SND_CARD_UN_INIT;
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;
}
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);