Android音訊驅動-ASOC之PCM HW Params
阿新 • • 發佈:2019-01-09
ALSA的HW_param流程
soc_pcm_hw_params => rtd->dai_link->ops->hw_params => codec_dai->driver->ops->hw_params =>
cpu_dai->driver->ops->hw_params => platform->driver->ops->hw_params
struct pcm *pcm_open(unsigned int card, unsigned int device,
unsigned int flags, struct pcm_config *config)
{
struct pcm *pcm;
struct snd_pcm_info info;
struct snd_pcm_hw_params params;
struct snd_pcm_sw_params sparams;
char fn[256 ];
int rc;
pcm = calloc(1, sizeof(struct pcm));
if (!pcm || !config)
return &bad_pcm; /* TODO: could support default config here */
pcm->config = *config;
snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
flags & PCM_IN ? 'c' : 'p');
pcm->flags = flags;
pcm->fd = open(fn, O_RDWR );
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {
oops(pcm, errno, "cannot get info");
goto fail_close;
}
param_init(¶ms);
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT,
pcm_format_to_alsa(config->format));
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT ,
SNDRV_PCM_SUBFORMAT_STD);
param_set_min(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
pcm_format_to_bits(config->format));
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_FRAME_BITS,
pcm_format_to_bits(config->format) * config->channels);
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS,
config->channels);
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_RATE, config->rate);
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS,
SNDRV_PCM_ACCESS_RW_INTERLEAVED);
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) {
oops(pcm, errno, "cannot set hw params");
goto fail_close;
}
/* get our refined hw_params */
config->period_size = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
config->period_count = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS);
pcm->buffer_size = config->period_count * config->period_size;
memset(&sparams, 0, sizeof(sparams));
sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;
sparams.period_step = 1;
if (!config->start_threshold) {
if (pcm->flags & PCM_IN)
pcm->config.start_threshold = sparams.start_threshold = 1;
else
pcm->config.start_threshold = sparams.start_threshold =
config->period_count * config->period_size / 2;
} else
sparams.start_threshold = config->start_threshold;
/* pick a high stop threshold - todo: does this need further tuning */
if (!config->stop_threshold) {
if (pcm->flags & PCM_IN)
pcm->config.stop_threshold = sparams.stop_threshold =
config->period_count * config->period_size * 10;
else
pcm->config.stop_threshold = sparams.stop_threshold =
config->period_count * config->period_size;
}
else
sparams.stop_threshold = config->stop_threshold;
if (!pcm->config.avail_min) {
if (pcm->flags & PCM_MMAP)
pcm->config.avail_min = sparams.avail_min = pcm->config.period_size;
else
pcm->config.avail_min = sparams.avail_min = 1;
} else
sparams.avail_min = config->avail_min;
sparams.xfer_align = config->period_size / 2; /* needed for old kernels */
sparams.silence_threshold = config->silence_threshold;
sparams.silence_size = config->silence_size;
pcm->boundary = sparams.boundary = pcm->buffer_size;
while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size)
pcm->boundary *= 2;
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {
oops(pcm, errno, "cannot set sw params");
goto fail;
}
rc = pcm_hw_mmap_status(pcm);
pcm->underruns = 0;
return pcm;
}
static int snd_pcm_common_ioctl1(struct file *file,
struct snd_pcm_substream *substream,
unsigned int cmd, void __user *arg)
{
switch (cmd) {
case SNDRV_PCM_IOCTL_HW_PARAMS:
return snd_pcm_hw_params_user(substream, arg);
}
static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params __user * _params)
{
struct snd_pcm_hw_params *params;
int err;
params = memdup_user(_params, sizeof(*params));
err = snd_pcm_hw_params(substream, params);
if (copy_to_user(_params, params, sizeof(*params))) {
if (!err)
err = -EFAULT;
}
kfree(params);
return err;
}
static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime;
int err, usecs;
unsigned int bits;
snd_pcm_uframes_t frames;
runtime = substream->runtime;
snd_pcm_stream_lock_irq(substream);
switch (runtime->status->state) {
case SNDRV_PCM_STATE_OPEN:
case SNDRV_PCM_STATE_SETUP:
case SNDRV_PCM_STATE_PREPARED:
break;
default:
snd_pcm_stream_unlock_irq(substream);
return -EBADFD;
}
snd_pcm_stream_unlock_irq(substream);
if (atomic_read(&substream->mmap_count))
return -EBADFD;
params->rmask = ~0U;
err = snd_pcm_hw_refine(substream, params);
err = snd_pcm_hw_params_choose(substream, params);
if (substream->ops->hw_params != NULL) {
err = substream->ops->hw_params(substream, params);
}
runtime->access = params_access(params);
runtime->format = params_format(params);
runtime->subformat = params_subformat(params);
runtime->channels = params_channels(params);
runtime->rate = params_rate(params);
runtime->period_size = params_period_size(params);
runtime->periods = params_periods(params);
runtime->buffer_size = params_buffer_size(params);
runtime->info = params->info;
runtime->rate_num = params->rate_num;
runtime->rate_den = params->rate_den;
runtime->no_period_wakeup =
(params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) &&
(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP);
bits = snd_pcm_format_physical_width(runtime->format);
runtime->sample_bits = bits;
bits *= runtime->channels;
runtime->frame_bits = bits;
frames = 1;
while (bits % 8 != 0) {
bits *= 2;
frames *= 2;
}
runtime->byte_align = bits / 8;
runtime->min_align = frames;
/* Default sw params */
runtime->tstamp_mode = SNDRV_PCM_TSTAMP_NONE;
runtime->period_step = 1;
runtime->control->avail_min = runtime->period_size;
runtime->start_threshold = 1;
runtime->stop_threshold = runtime->buffer_size;
runtime->silence_threshold = 0;
runtime->silence_size = 0;
runtime->boundary = runtime->buffer_size;
while (runtime->boundary * 2 <= LONG_MAX - runtime->buffer_size)
runtime->boundary *= 2;
snd_pcm_timer_resolution_change(substream);
snd_pcm_set_state(substream, SNDRV_PCM_STATE_SETUP);
if (pm_qos_request_active(&substream->latency_pm_qos_req))
pm_qos_remove_request(&substream->latency_pm_qos_req);
if ((usecs = period_to_usecs(runtime)) >= 0)
pm_qos_add_request(&substream->latency_pm_qos_req,
PM_QOS_CPU_DMA_LATENCY, usecs);
return 0;
}
static int soc_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_platform *platform = rtd->platform;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int i, ret = 0;
mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass);
ret = soc_pcm_params_symmetry(substream, params);
if (rtd->dai_link->ops && rtd->dai_link->ops->hw_params) {
//dai link driver沒有實現hw_params
ret = rtd->dai_link->ops->hw_params(substream, params);
}
for (i = 0; i < rtd->num_codecs; i++) {
struct snd_soc_dai *codec_dai = rtd->codec_dais[i];
struct snd_pcm_hw_params codec_params;
/* copy params for each codec */
codec_params = *params;
/* fixup params based on TDM slot masks */
if (codec_dai->tx_mask)
soc_pcm_codec_params_fixup(&codec_params, codec_dai->tx_mask);
if (codec_dai->rx_mask)
soc_pcm_codec_params_fixup(&codec_params, codec_dai->rx_mask);
//codec dai driver沒有實現hw_params
ret = soc_dai_hw_params(substream, &codec_params, codec_dai);
codec_dai->rate = params_rate(&codec_params);
codec_dai->channels = params_channels(&codec_params);
codec_dai->sample_bits = snd_pcm_format_physical_width(
params_format(&codec_params));
}
//cpu dai driver沒有實現hw_params
ret = soc_dai_hw_params(substream, params, cpu_dai);
if (platform->driver->ops && platform->driver->ops->hw_params) {
//呼叫platform driver的hw_params函式
ret = platform->driver->ops->hw_params(substream, params);
}
/* store the parameters for each DAIs */
cpu_dai->rate = params_rate(params);
cpu_dai->channels = params_channels(params);
cpu_dai->sample_bits =
snd_pcm_format_physical_width(params_format(params));
out:
mutex_unlock(&rtd->pcm_mutex);
return ret;
}
//呼叫cpu dai driver和codec dai driver的具體實現
int soc_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
int ret;
if (dai->driver->ops && dai->driver->ops->hw_params) {
ret = dai->driver->ops->hw_params(substream, params, dai);
}
return 0;
}
platform driver例項的實現
static int mtk_pcm_I2S0dl1_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
int ret = 0;
substream->runtime->dma_bytes = params_buffer_bytes(hw_params);
if (mPlaybackSramState == SRAM_STATE_PLAYBACKFULL) {
substream->runtime->dma_area = (unsigned char *)Get_Afe_SramBase_Pointer();
substream->runtime->dma_addr = AFE_INTERNAL_SRAM_PHY_BASE;
SetHighAddr(Soc_Aud_Digital_Block_MEM_DL1, false);
AudDrv_Allocate_DL1_Buffer(mDev, substream->runtime->dma_bytes);
} else {
substream->runtime->dma_bytes = params_buffer_bytes(hw_params);
substream->runtime->dma_area = Dl1_Playback_dma_buf->area;
substream->runtime->dma_addr = Dl1_Playback_dma_buf->addr;
SetHighAddr(Soc_Aud_Digital_Block_MEM_DL1, true);
SetDL1Buffer(substream, hw_params);
}
return ret;
}