📄 soc-core.c
字号:
/* * soc-core.c -- ALSA SoC Audio Layer * * Copyright 2005 Wolfson Microelectronics PLC. * Copyright 2005 Openedhand Ltd. * * Author: Liam Girdwood * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com * with code, comments and ideas from :- * Richard Purdie <richard@openedhand.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; either version 2 of the License, or (at your * option) any later version. * * Revision history * 12th Aug 2005 Initial version. * 25th Oct 2005 Working Codec, Interface and Platform registration. * * TODO: * o Add hw rules to enforce rates, etc. * o More testing with other codecs/machines. * o Add more codecs and platforms to ensure good API coverage. * o Support TDM on PCM and I2S */#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/pm.h>#include <linux/bitops.h>#include <linux/platform_device.h>#include <sound/driver.h>#include <sound/core.h>#include <sound/pcm.h>#include <sound/pcm_params.h>#include <sound/soc.h>#include <sound/soc-dapm.h>#include <sound/initval.h>/* debug */#define SOC_DEBUG 0#if SOC_DEBUG#define dbg(format, arg...) printk(format, ## arg)#else#define dbg(format, arg...)#endifstatic DEFINE_MUTEX(pcm_mutex);static DEFINE_MUTEX(io_mutex);static DECLARE_WAIT_QUEUE_HEAD(soc_pm_waitq);/* * This is a timeout to do a DAPM powerdown after a stream is closed(). * It can be used to eliminate pops between different playback streams, e.g. * between two audio tracks. */static int pmdown_time = 5000;module_param(pmdown_time, int, 0);MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)");/* * This function forces any delayed work to be queued and run. */static int run_delayed_work(struct delayed_work *dwork){ int ret; /* cancel any work waiting to be queued. */ ret = cancel_delayed_work(dwork); /* if there was any work waiting then we run it now and * wait for it's completion */ if (ret) { schedule_delayed_work(dwork, 0); flush_scheduled_work(); } return ret;}#ifdef CONFIG_SND_SOC_AC97_BUS/* unregister ac97 codec */static int soc_ac97_dev_unregister(struct snd_soc_codec *codec){ if (codec->ac97->dev.bus) device_unregister(&codec->ac97->dev); return 0;}/* stop no dev release warning */static void soc_ac97_device_release(struct device *dev){}/* register ac97 codec to bus */static int soc_ac97_dev_register(struct snd_soc_codec *codec){ int err; codec->ac97->dev.bus = &ac97_bus_type; codec->ac97->dev.parent = NULL; codec->ac97->dev.release = soc_ac97_device_release; snprintf(codec->ac97->dev.bus_id, BUS_ID_SIZE, "%d-%d:%s", codec->card->number, 0, codec->name); err = device_register(&codec->ac97->dev); if (err < 0) { snd_printk(KERN_ERR "Can't register ac97 bus\n"); codec->ac97->dev.bus = NULL; return err; } return 0;}#endifstatic inline const char* get_dai_name(int type){ switch(type) { case SND_SOC_DAI_AC97_BUS: case SND_SOC_DAI_AC97: return "AC97"; case SND_SOC_DAI_I2S: return "I2S"; case SND_SOC_DAI_PCM: return "PCM"; } return NULL;}/* * Called by ALSA when a PCM substream is opened, the runtime->hw record is * then initialized and any private data can be allocated. This also calls * startup for the cpu DAI, platform, machine and codec DAI. */static int soc_pcm_open(struct snd_pcm_substream *substream){ struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_dai_link *machine = rtd->dai; struct snd_soc_platform *platform = socdev->platform; struct snd_soc_cpu_dai *cpu_dai = machine->cpu_dai; struct snd_soc_codec_dai *codec_dai = machine->codec_dai; int ret = 0; mutex_lock(&pcm_mutex); /* startup the audio subsystem */ if (cpu_dai->ops.startup) { ret = cpu_dai->ops.startup(substream); if (ret < 0) { printk(KERN_ERR "asoc: can't open interface %s\n", cpu_dai->name); goto out; } } if (platform->pcm_ops->open) { ret = platform->pcm_ops->open(substream); if (ret < 0) { printk(KERN_ERR "asoc: can't open platform %s\n", platform->name); goto platform_err; } } if (codec_dai->ops.startup) { ret = codec_dai->ops.startup(substream); if (ret < 0) { printk(KERN_ERR "asoc: can't open codec %s\n", codec_dai->name); goto codec_dai_err; } } if (machine->ops && machine->ops->startup) { ret = machine->ops->startup(substream); if (ret < 0) { printk(KERN_ERR "asoc: %s startup failed\n", machine->name); goto machine_err; } } /* Check that the codec and cpu DAI's are compatible */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { runtime->hw.rate_min = max(codec_dai->playback.rate_min, cpu_dai->playback.rate_min); runtime->hw.rate_max = min(codec_dai->playback.rate_max, cpu_dai->playback.rate_max); runtime->hw.channels_min = max(codec_dai->playback.channels_min, cpu_dai->playback.channels_min); runtime->hw.channels_max = min(codec_dai->playback.channels_max, cpu_dai->playback.channels_max); runtime->hw.formats = codec_dai->playback.formats & cpu_dai->playback.formats; runtime->hw.rates = codec_dai->playback.rates & cpu_dai->playback.rates; } else { runtime->hw.rate_min = max(codec_dai->capture.rate_min, cpu_dai->capture.rate_min); runtime->hw.rate_max = min(codec_dai->capture.rate_max, cpu_dai->capture.rate_max); runtime->hw.channels_min = max(codec_dai->capture.channels_min, cpu_dai->capture.channels_min); runtime->hw.channels_max = min(codec_dai->capture.channels_max, cpu_dai->capture.channels_max); runtime->hw.formats = codec_dai->capture.formats & cpu_dai->capture.formats; runtime->hw.rates = codec_dai->capture.rates & cpu_dai->capture.rates; } snd_pcm_limit_hw_rates(runtime); if (!runtime->hw.rates) { printk(KERN_ERR "asoc: %s <-> %s No matching rates\n", codec_dai->name, cpu_dai->name); goto machine_err; } if (!runtime->hw.formats) { printk(KERN_ERR "asoc: %s <-> %s No matching formats\n", codec_dai->name, cpu_dai->name); goto machine_err; } if (!runtime->hw.channels_min || !runtime->hw.channels_max) { printk(KERN_ERR "asoc: %s <-> %s No matching channels\n", codec_dai->name, cpu_dai->name); goto machine_err; } dbg("asoc: %s <-> %s info:\n",codec_dai->name, cpu_dai->name); dbg("asoc: rate mask 0x%x\n", runtime->hw.rates); dbg("asoc: min ch %d max ch %d\n", runtime->hw.channels_min, runtime->hw.channels_max); dbg("asoc: min rate %d max rate %d\n", runtime->hw.rate_min, runtime->hw.rate_max); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) cpu_dai->playback.active = codec_dai->playback.active = 1; else cpu_dai->capture.active = codec_dai->capture.active = 1; cpu_dai->active = codec_dai->active = 1; cpu_dai->runtime = runtime; socdev->codec->active++; mutex_unlock(&pcm_mutex); return 0;machine_err: if (machine->ops && machine->ops->shutdown) machine->ops->shutdown(substream);codec_dai_err: if (platform->pcm_ops->close) platform->pcm_ops->close(substream);platform_err: if (cpu_dai->ops.shutdown) cpu_dai->ops.shutdown(substream);out: mutex_unlock(&pcm_mutex); return ret;}/* * Power down the audio subsystem pmdown_time msecs after close is called. * This is to ensure there are no pops or clicks in between any music tracks * due to DAPM power cycling. */static void close_delayed_work(struct work_struct *work){ struct snd_soc_device *socdev = container_of(work, struct snd_soc_device, delayed_work.work); struct snd_soc_codec *codec = socdev->codec; struct snd_soc_codec_dai *codec_dai; int i; mutex_lock(&pcm_mutex); for(i = 0; i < codec->num_dai; i++) { codec_dai = &codec->dai[i]; dbg("pop wq checking: %s status: %s waiting: %s\n", codec_dai->playback.stream_name, codec_dai->playback.active ? "active" : "inactive", codec_dai->pop_wait ? "yes" : "no"); /* are we waiting on this codec DAI stream */ if (codec_dai->pop_wait == 1) { codec_dai->pop_wait = 0; snd_soc_dapm_stream_event(codec, codec_dai->playback.stream_name, SND_SOC_DAPM_STREAM_STOP); /* power down the codec power domain if no longer active */ if (codec->active == 0) { dbg("pop wq D3 %s %s\n", codec->name, codec_dai->playback.stream_name); if (codec->dapm_event) codec->dapm_event(codec, SNDRV_CTL_POWER_D3hot); } } } mutex_unlock(&pcm_mutex);}/* * Called by ALSA when a PCM substream is closed. Private data can be * freed here. The cpu DAI, codec DAI, machine and platform are also * shutdown. */static int soc_codec_close(struct snd_pcm_substream *substream){ struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_dai_link *machine = rtd->dai; struct snd_soc_platform *platform = socdev->platform; struct snd_soc_cpu_dai *cpu_dai = machine->cpu_dai; struct snd_soc_codec_dai *codec_dai = machine->codec_dai; struct snd_soc_codec *codec = socdev->codec; mutex_lock(&pcm_mutex); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) cpu_dai->playback.active = codec_dai->playback.active = 0; else cpu_dai->capture.active = codec_dai->capture.active = 0; if (codec_dai->playback.active == 0 && codec_dai->capture.active == 0) { cpu_dai->active = codec_dai->active = 0; } codec->active--; if (cpu_dai->ops.shutdown) cpu_dai->ops.shutdown(substream); if (codec_dai->ops.shutdown) codec_dai->ops.shutdown(substream); if (machine->ops && machine->ops->shutdown) machine->ops->shutdown(substream); if (platform->pcm_ops->close) platform->pcm_ops->close(substream); cpu_dai->runtime = NULL; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { /* start delayed pop wq here for playback streams */ codec_dai->pop_wait = 1; schedule_delayed_work(&socdev->delayed_work, msecs_to_jiffies(pmdown_time)); } else { /* capture streams can be powered down now */ snd_soc_dapm_stream_event(codec, codec_dai->capture.stream_name, SND_SOC_DAPM_STREAM_STOP); if (codec->active == 0 && codec_dai->pop_wait == 0){ if (codec->dapm_event) codec->dapm_event(codec, SNDRV_CTL_POWER_D3hot); } } mutex_unlock(&pcm_mutex); return 0;}/* * Called by ALSA when the PCM substream is prepared, can set format, sample * rate, etc. This function is non atomic and can be called multiple times, * it can refer to the runtime info. */static int soc_pcm_prepare(struct snd_pcm_substream *substream){ struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_dai_link *machine = rtd->dai; struct snd_soc_platform *platform = socdev->platform; struct snd_soc_cpu_dai *cpu_dai = machine->cpu_dai; struct snd_soc_codec_dai *codec_dai = machine->codec_dai; struct snd_soc_codec *codec = socdev->codec; int ret = 0; mutex_lock(&pcm_mutex); if (machine->ops && machine->ops->prepare) { ret = machine->ops->prepare(substream); if (ret < 0) { printk(KERN_ERR "asoc: machine prepare error\n"); goto out; } } if (platform->pcm_ops->prepare) { ret = platform->pcm_ops->prepare(substream); if (ret < 0) { printk(KERN_ERR "asoc: platform prepare error\n"); goto out; } } if (codec_dai->ops.prepare) { ret = codec_dai->ops.prepare(substream); if (ret < 0) { printk(KERN_ERR "asoc: codec DAI prepare error\n"); goto out; } } if (cpu_dai->ops.prepare) { ret = cpu_dai->ops.prepare(substream); if (ret < 0) { printk(KERN_ERR "asoc: cpu DAI prepare error\n"); goto out; } } /* we only want to start a DAPM playback stream if we are not waiting * on an existing one stopping */ if (codec_dai->pop_wait) { /* we are waiting for the delayed work to start */ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) snd_soc_dapm_stream_event(socdev->codec, codec_dai->capture.stream_name, SND_SOC_DAPM_STREAM_START); else { codec_dai->pop_wait = 0; cancel_delayed_work(&socdev->delayed_work); if (codec_dai->dai_ops.digital_mute) codec_dai->dai_ops.digital_mute(codec_dai, 0); } } else { /* no delayed work - do we need to power up codec */ if (codec->dapm_state != SNDRV_CTL_POWER_D0) { if (codec->dapm_event) codec->dapm_event(codec, SNDRV_CTL_POWER_D1); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) snd_soc_dapm_stream_event(codec, codec_dai->playback.stream_name, SND_SOC_DAPM_STREAM_START); else snd_soc_dapm_stream_event(codec, codec_dai->capture.stream_name, SND_SOC_DAPM_STREAM_START); if (codec->dapm_event) codec->dapm_event(codec, SNDRV_CTL_POWER_D0); if (codec_dai->dai_ops.digital_mute) codec_dai->dai_ops.digital_mute(codec_dai, 0); } else { /* codec already powered - power on widgets */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) snd_soc_dapm_stream_event(codec, codec_dai->playback.stream_name, SND_SOC_DAPM_STREAM_START); else snd_soc_dapm_stream_event(codec, codec_dai->capture.stream_name, SND_SOC_DAPM_STREAM_START); if (codec_dai->dai_ops.digital_mute) codec_dai->dai_ops.digital_mute(codec_dai, 0); } }out: mutex_unlock(&pcm_mutex); return ret;}/* * Called by ALSA when the hardware params are set by application. This * function can also be called multiple times and can allocate buffers * (using snd_pcm_lib_* ). It's non-atomic. */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_device *socdev = rtd->socdev; struct snd_soc_dai_link *machine = rtd->dai; struct snd_soc_platform *platform = socdev->platform; struct snd_soc_cpu_dai *cpu_dai = machine->cpu_dai; struct snd_soc_codec_dai *codec_dai = machine->codec_dai; int ret = 0; mutex_lock(&pcm_mutex); if (machine->ops && machine->ops->hw_params) { ret = machine->ops->hw_params(substream, params); if (ret < 0) { printk(KERN_ERR "asoc: machine hw_params failed\n"); goto out; } } if (codec_dai->ops.hw_params) { ret = codec_dai->ops.hw_params(substream, params); if (ret < 0) { printk(KERN_ERR "asoc: can't set codec %s hw params\n", codec_dai->name); goto codec_err; } } if (cpu_dai->ops.hw_params) { ret = cpu_dai->ops.hw_params(substream, params); if (ret < 0) { printk(KERN_ERR "asoc: can't set interface %s hw params\n", cpu_dai->name); goto interface_err; } } if (platform->pcm_ops->hw_params) { ret = platform->pcm_ops->hw_params(substream, params); if (ret < 0) { printk(KERN_ERR "asoc: can't set platform %s hw params\n", platform->name); goto platform_err; } }out: mutex_unlock(&pcm_mutex);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -