alsa-devel.alsa-project.org archive mirror
 help / color / mirror / Atom feed
From: Mike Looijmans <mike.looijmans@topic.nl>
To: "Bedia, Vaibhav" <vaibhav.bedia@ti.com>
Cc: "alsa-devel@alsa-project.org" <alsa-devel@alsa-project.org>
Subject: Re: [PATCH v2] davinci-mcasp: Add support for multichannel playback
Date: Wed, 10 Apr 2013 13:08:14 +0200	[thread overview]
Message-ID: <5165481E.2000200@topic.nl> (raw)
In-Reply-To: <B5906170F1614E41A8A28DE3B8D121433ED4049D@DBDE01.ent.ti.com>

[-- Attachment #1: Type: text/plain, Size: 1558 bytes --]

On 03/11/2013 09:56 AM, Bedia, Vaibhav wrote:
> On Mon, Mar 11, 2013 at 14:03:50, Mike Looijmans wrote:
>>
>> I'll post the code, but it's based on a 2.6.37 kernel and it's tailored
>> to "my" platform, with numerous changes to clock settings and such. So
>> don't expect it to be useful. It's only interesting as an "example" or
>> for illustrating the general idea, but entirely useless for inclusion in
>> mainstream.
>
> Thanks. In my opinion any proof of concept will be good to get some
> discussion going on this topic.

It has been way to long since I last replied, and there's still much to do.

Anyway, I attached the "multiplex" codec source code. The multiplex 
assumes that all its child codecs share the same (cpu) dai and that the 
dai is capable of handling multiple codecs (either using TDM or multiple 
pins, or whatever combination). The machine part must take care of 
forwarding the "mask" setting of the multiplexer and translate it to 
something the CPU DAI understands.
(If the codecs do not share the DAI, you don't need the multiplexer...)

The multiplex only supports capture.

To use it, compile it in (change Kconfig) and have the board file 
register codecs with the multiplexer. The multiplexer creates a mixer 
switch control for each codec. To use a set of codecs, set their switch 
to "on" (which is default) and record from the multiplex codec. The 
multiplex will control the codecs.

Codecs can also be used without the multiplex in between, no change there.

See it as a proof of concept. It works fine for me...

Mike.

[-- Attachment #2: multiplex.c --]
[-- Type: text/x-csrc, Size: 12131 bytes --]

/*
*/

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/slab.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>
#include <sound/tlv.h>

#include "multiplex.h"

#define MULTIPLEX_INPUT_SELECT	0x10

struct multiplex_priv {
	struct snd_soc_dai **dai_list;
	unsigned int* active_codec_mask;
	struct snd_kcontrol_new dapm_mixer_inputs[MULTIPLEX_MAX_NR_CODECS];
	struct soc_mixer_control dapm_mixer_inputs_private[MULTIPLEX_MAX_NR_CODECS];
	struct snd_soc_dapm_widget dapm_mixer;
};


static int multiplex_write(struct snd_soc_codec *codec, unsigned int reg,
				unsigned int val)
{
	struct multiplex_priv *multiplex = snd_soc_codec_get_drvdata(codec);

	/*printk(KERN_WARNING "%s (reg=0x%x, val=0x%x)\n", __FUNCTION__, reg, val);*/
	switch(reg)
	{
		case MULTIPLEX_INPUT_SELECT:
			*multiplex->active_codec_mask = val;
			return 0;
	}
	return 0;
}

static unsigned int multiplex_read(struct snd_soc_codec *codec, unsigned int reg)
{
	struct multiplex_priv *multiplex = snd_soc_codec_get_drvdata(codec);

	/*printk(KERN_WARNING "%s (reg=0x%x)\n", __FUNCTION__, reg);*/
	switch(reg)
	{
		case MULTIPLEX_INPUT_SELECT:
			return *multiplex->active_codec_mask;
	}
	return 0;
}

static const struct snd_kcontrol_new input_mixer_controls[] = {
	SOC_DAPM_SINGLE("Switch 0", MULTIPLEX_INPUT_SELECT, 0, 1, 0),
	SOC_DAPM_SINGLE("Switch 1", MULTIPLEX_INPUT_SELECT, 1, 1, 0),
	SOC_DAPM_SINGLE("Switch 2", MULTIPLEX_INPUT_SELECT, 2, 1, 0),
	SOC_DAPM_SINGLE("Switch 3", MULTIPLEX_INPUT_SELECT, 3, 1, 0),
	SOC_DAPM_SINGLE("Switch 4", MULTIPLEX_INPUT_SELECT, 4, 1, 0),
	SOC_DAPM_SINGLE("Switch 5", MULTIPLEX_INPUT_SELECT, 5, 1, 0),
	SOC_DAPM_SINGLE("Switch 6", MULTIPLEX_INPUT_SELECT, 6, 1, 0),
};


static const struct snd_soc_dapm_widget multiplex_dapm_widgets[] = {
	SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0),
	SND_SOC_DAPM_INPUT("IN"),
};

static const struct snd_soc_dapm_route multiplex_dapm_routes[] = {
/*	{"Input Mixer", "Switch 0", "IN"},
	{"Input Mixer", "Switch 1", "IN"},
	{"Input Mixer", "Switch 2", "IN"},
	{"Input Mixer", "Switch 3", "IN"},
	{"Input Mixer", "Switch 4", "IN"},
	{"Input Mixer", "Switch 5", "IN"},
	{"Input Mixer", "Switch 6", "IN"},*/
	{"ADC", NULL, "Input Mixer"},
};

static int multiplex_set_dai_sysclk(struct snd_soc_dai *dai,
                                int clk_id, unsigned int freq, int dir)
{
	struct snd_soc_codec *codec = dai->codec;
	struct multiplex_priv *multiplex = snd_soc_codec_get_drvdata(codec);
	unsigned int active_codec_mask = *multiplex->active_codec_mask;
	unsigned int i;
	int ret;

	printk(KERN_DEBUG "%s(%d, %u)", __func__, clk_id, freq);

	for (i = 0; multiplex->dai_list[i] != NULL; ++i)
	{
		if (active_codec_mask & BIT(i))
		{
			struct snd_soc_dai *codec_dai =  multiplex->dai_list[i];
			if (codec_dai->driver->ops->set_sysclk) {
				ret = codec_dai->driver->ops->set_sysclk(codec_dai, clk_id, freq, dir);
				if (ret < 0) {
					printk(KERN_ERR "multiplex: can't set codec %s sysclk\n",
						codec_dai->name);
					return ret;
				}
			}
		}
	}

	return 0; /* We don't care */
}

static int multiplex_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
{
	/* We only support I2S, just check if that's set and do nothing */
	if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBM_CFM)
		return -EINVAL;
	if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S)
		return -EINVAL;

	return 0;
}

static int multiplex_hw_params(struct snd_pcm_substream *substream,
			     struct snd_pcm_hw_params *params,
			     struct snd_soc_dai *dai)
{
	struct snd_soc_codec *codec = dai->codec;
	struct multiplex_priv *multiplex = snd_soc_codec_get_drvdata(codec);
	unsigned int active_codec_mask = *multiplex->active_codec_mask;
	unsigned int i;
	int ret;

	for (i = 0; multiplex->dai_list[i] != NULL; ++i)
	{
		if (active_codec_mask & BIT(i))
		{
			struct snd_soc_dai *codec_dai =  multiplex->dai_list[i];
			if (codec_dai->driver->ops->hw_params) {
				ret = codec_dai->driver->ops->hw_params(substream, params, codec_dai);
				if (ret < 0) {
					printk(KERN_ERR "multiplex: can't set codec %s hw params\n",
						codec_dai->name);
					return ret;
				}
			}
		}
	}

	return 0;
}


static void multiplex_shutdown(struct snd_pcm_substream *substream,
				 struct snd_soc_dai *dai)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_codec *codec = rtd->codec;
	struct multiplex_priv *multiplex = snd_soc_codec_get_drvdata(codec);
	unsigned int active_codec_mask = *multiplex->active_codec_mask;
	unsigned int i;
	struct snd_soc_pcm_runtime rtd_fake;
	const char* stream_name = rtd->codec_dai->driver->capture.stream_name;

	printk(KERN_DEBUG "%s (%s)\n", __func__, stream_name);

	/* snd_soc_dapm_stream_event only uses the "codec" field of the
	 * rtd struct, that's why this trick works */
	rtd_fake.pcm = rtd->pcm;
	rtd_fake.platform = rtd->platform;
	rtd_fake.codec_dai = rtd->codec_dai;
	rtd_fake.cpu_dai = rtd->cpu_dai;


	for (i = 0; multiplex->dai_list[i] != NULL; ++i)
	{
		if (active_codec_mask & BIT(i))
		{
			struct snd_soc_dai *codec_dai =  multiplex->dai_list[i];
			rtd_fake.codec = codec_dai->codec;
			/* Send shutdown event first (just like core) */
			if (codec_dai->driver->ops->shutdown) {
				codec_dai->driver->ops->shutdown(substream, codec_dai);
			}
			/* Tell DAPM we don't need the path any longer */
			snd_soc_dapm_stream_event(&rtd_fake, stream_name, SND_SOC_DAPM_STREAM_STOP);
		}
	}
}

static int multiplex_mute(struct snd_soc_dai *dai, int mute)
{
	struct snd_soc_codec *codec = dai->codec;
	struct multiplex_priv *multiplex = snd_soc_codec_get_drvdata(codec);
	unsigned int active_codec_mask = *multiplex->active_codec_mask;
	unsigned int i;
	int ret = 0;

	for (i = 0; multiplex->dai_list[i] != NULL; ++i)
	{
		if (active_codec_mask & BIT(i))
		{
			struct snd_soc_dai *codec_dai =  multiplex->dai_list[i];
			if (codec_dai->driver->ops->digital_mute) {
				int r =codec_dai->driver->ops->digital_mute(codec_dai, mute);
				if (r != 0)
					ret = r;
			}
		}
	}

	return ret;
}


static int multiplex_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct multiplex_priv *multiplex = snd_soc_codec_get_drvdata(rtd->codec);
	unsigned int active_codec_mask = *multiplex->active_codec_mask;
	unsigned int i;
	const char* stream_name = rtd->codec_dai->driver->capture.stream_name;
	struct snd_soc_pcm_runtime rtd_fake;

	printk(KERN_DEBUG "%s (%s)\n", __func__, stream_name);

	/* snd_soc_dapm_stream_event only uses the "codec" field of the
	 * rtd struct, that's why this trick works */
	rtd_fake.pcm = rtd->pcm;
	rtd_fake.platform = rtd->platform;
	rtd_fake.codec_dai = rtd->codec_dai;
	rtd_fake.cpu_dai = rtd->cpu_dai;

	for (i = 0; multiplex->dai_list[i] != NULL; ++i)
	{
		if (active_codec_mask & BIT(i))
		{
			struct snd_soc_codec *client =  multiplex->dai_list[i]->codec;
			if (!client)
			{
				printk(KERN_WARNING "multiplex: No CODEC for %d\n", i);
				continue;
			}
			rtd_fake.codec = client;
			snd_soc_dapm_stream_event(&rtd_fake, stream_name, SND_SOC_DAPM_STREAM_START);
		}
	}

	return 0;
}


#define MULTIPLEX_RATES	(SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_96000)
#define MULTIPLEX_FORMATS	(SNDRV_PCM_FMTBIT_S16_LE \
			 | SNDRV_PCM_FMTBIT_S20_3LE \
			 | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE \
			 | SNDRV_PCM_FMTBIT_S32_LE \
			 )

static struct snd_soc_dai_ops multiplex_ops = {
	.set_sysclk = multiplex_set_dai_sysclk,
	.hw_params = multiplex_hw_params,
	.shutdown = multiplex_shutdown,
	.digital_mute = multiplex_mute,
	.set_fmt = multiplex_set_dai_fmt,
	.prepare = multiplex_prepare,
};

static struct snd_soc_dai_driver multiplex_dais[] = {
	{
	.name = "multiplex-adc",
	.capture = {
		    .stream_name = "Capture",
		    .channels_min = 2,
		    .channels_max = 2 * MULTIPLEX_MAX_NR_CODECS,
		    .rates = MULTIPLEX_RATES,
		    .rate_min = 8000,
		    .rate_max = 51200,
		    .formats = MULTIPLEX_FORMATS,},
	.ops = &multiplex_ops,
	.symmetric_rates = 1,
	},
};

static int multiplex_probe(struct snd_soc_codec *codec)
{
	struct multiplex_priv *multiplex = snd_soc_codec_get_drvdata(codec);
	unsigned int i;
	unsigned int num_children;

	codec->idle_bias_off = 1;

	/* Add some default routes for DAPM so that it knows we're ready to go */
	snd_soc_dapm_new_controls(codec, multiplex_dapm_widgets,
				ARRAY_SIZE(multiplex_dapm_widgets));
	for (i = 0; multiplex->dai_list[i] != NULL; ++i)
	{
		printk(KERN_DEBUG "multiplex %d: dai=%s codec=%s\n", i, multiplex->dai_list[i]->name, multiplex->dai_list[i]->codec->name);
		multiplex->dapm_mixer_inputs_private[i].reg = MULTIPLEX_INPUT_SELECT;
		multiplex->dapm_mixer_inputs_private[i].shift = i;
		multiplex->dapm_mixer_inputs_private[i].rshift = i;
		multiplex->dapm_mixer_inputs_private[i].max = 1;
		multiplex->dapm_mixer_inputs_private[i].platform_max = 1;
		multiplex->dapm_mixer_inputs_private[i].invert = 0;
		multiplex->dapm_mixer_inputs[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER;
		multiplex->dapm_mixer_inputs[i].name = (char*)multiplex->dai_list[i]->codec->name;
		multiplex->dapm_mixer_inputs[i].info = snd_soc_info_volsw;
		multiplex->dapm_mixer_inputs[i].get = snd_soc_dapm_get_volsw;
		multiplex->dapm_mixer_inputs[i].put = snd_soc_dapm_put_volsw;
		multiplex->dapm_mixer_inputs[i].private_value = (unsigned long)&multiplex->dapm_mixer_inputs_private[i];
	}
	num_children = i;
	multiplex->dapm_mixer.id = snd_soc_dapm_mixer_named_ctl; /* or 'snd_soc_dapm_mixer' for a prefix*/
	multiplex->dapm_mixer.name = "Input Mixer";
	multiplex->dapm_mixer.reg = SND_SOC_NOPM;
	multiplex->dapm_mixer.shift = 0;
	multiplex->dapm_mixer.invert = 0;
	multiplex->dapm_mixer.kcontrols = multiplex->dapm_mixer_inputs;
	multiplex->dapm_mixer.num_kcontrols = num_children;
	snd_soc_dapm_new_controls(codec, &multiplex->dapm_mixer, 1);
	/* Set up route for switch (Input Mixer must exist when we do this) */
	for (i = 0; multiplex->dai_list[i] != NULL; ++i)
	{
		struct snd_soc_dapm_route this_route =
			{"Input Mixer", multiplex->dai_list[i]->codec->name, "IN"};
		snd_soc_dapm_add_routes(codec, &this_route, 1);
	}
	snd_soc_dapm_add_routes(codec, multiplex_dapm_routes,
				ARRAY_SIZE(multiplex_dapm_routes));
	snd_soc_dapm_enable_pin(codec, "IN");
	snd_soc_dapm_new_widgets(codec);
	return 0;
}


static struct snd_soc_codec_driver soc_codec_dev_multiplex = {
	.read = multiplex_read,
	.write = multiplex_write,
	.probe = multiplex_probe,
};

static __devinit int multiplex_platform_probe(struct platform_device *pdev)
{
	struct multiplex_priv *multiplex;
	int ret;
	struct multiplex_platform_data *pdata = pdev->dev.platform_data;

	multiplex = kzalloc(sizeof(struct multiplex_priv), GFP_KERNEL);
	if (multiplex == NULL)
		return -ENOMEM;

	multiplex->dai_list = pdata->dai_list;
	multiplex->active_codec_mask = pdata->active_mask;

	platform_set_drvdata(pdev, multiplex);

	ret = snd_soc_register_codec(&pdev->dev,
			&soc_codec_dev_multiplex, multiplex_dais, ARRAY_SIZE(multiplex_dais));
	if (ret < 0)
		kfree(multiplex);
	return ret;
}

static __devexit int multiplex_platform_remove(struct platform_device *pdev)
{
	snd_soc_unregister_codec(&pdev->dev);
	kfree(platform_get_drvdata(pdev));
	return 0;
}

static struct platform_driver multiplex_codec_driver = {
	.driver = {
			.name = "multiplex-codec",
			.owner = THIS_MODULE,
	},
	.probe = multiplex_platform_probe,
	.remove = __devexit_p(multiplex_platform_remove),
};

static int __init multiplex_modinit(void)
{
	int ret;
	ret = platform_driver_register(&multiplex_codec_driver);
	return ret;
}
module_init(multiplex_modinit);

static void __exit multiplex_exit(void)
{
	platform_driver_unregister(&multiplex_codec_driver);
}
module_exit(multiplex_exit);

MODULE_DESCRIPTION("ASoC multiplex codec driver");
MODULE_AUTHOR("Mike Looijmans");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:multiplex-codec");

[-- Attachment #3: multiplex.h --]
[-- Type: text/x-chdr, Size: 132 bytes --]


#define MULTIPLEX_MAX_NR_CODECS	8

struct multiplex_platform_data {
	struct snd_soc_dai **dai_list;
	unsigned int* active_mask;
};

[-- Attachment #4: Type: text/plain, Size: 0 bytes --]



  parent reply	other threads:[~2013-04-10 11:08 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-02-27 16:38 [PATCH v2] davinci-mcasp: Add support for multichannel playback Michal Bachraty
2013-03-05 11:06 ` Bedia, Vaibhav
2013-03-05 11:41   ` Michal Bachraty
2013-03-05 12:03     ` Bedia, Vaibhav
2013-03-05 12:24       ` Michal Bachraty
2013-03-05 16:01   ` Daniel Mack
2013-03-06 10:54     ` Bedia, Vaibhav
2013-03-06 10:57       ` Mark Brown
2013-03-06 11:19         ` Bedia, Vaibhav
2013-03-08  7:29           ` Mike Looijmans
2013-03-11  7:01             ` Bedia, Vaibhav
2013-03-11  8:33               ` Mike Looijmans
2013-03-11  8:56                 ` Bedia, Vaibhav
2013-04-05 13:21                   ` Can DAPM paths span multiple codecs/drivers? Mike Looijmans
2013-04-10 11:08                   ` Mike Looijmans [this message]
2013-03-06 11:00       ` [PATCH v2] davinci-mcasp: Add support for multichannel playback Daniel Mack
2013-03-06 11:19         ` Bedia, Vaibhav
2013-03-08 14:22           ` Mike Looijmans
2013-03-11  7:01             ` Bedia, Vaibhav
2013-03-06 14:27       ` Michal Bachraty
2013-03-06 14:31         ` Bedia, Vaibhav
2013-03-06 14:44     ` Michal Bachraty
2013-03-07 11:59       ` Bedia, Vaibhav
2013-03-08 13:11   ` Daniel Mack

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=5165481E.2000200@topic.nl \
    --to=mike.looijmans@topic.nl \
    --cc=alsa-devel@alsa-project.org \
    --cc=vaibhav.bedia@ti.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).