⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 soc-core.c

📁 linux 内核源代码
💻 C
📖 第 1 页 / 共 3 页
字号:
/* * 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 + -