ASoC: multi-component - ASoC Multi-Component Support
This patch extends the ASoC API to allow sound cards to have more than one
CODEC and more than one platform DMA controller. This is achieved by dividing
some current ASoC structures that contain both driver data and device data into
structures that only either contain device data or driver data. i.e.
struct snd_soc_codec ---> struct snd_soc_codec (device data)
+-> struct snd_soc_codec_driver (driver data)
struct snd_soc_platform ---> struct snd_soc_platform (device data)
+-> struct snd_soc_platform_driver (driver data)
struct snd_soc_dai ---> struct snd_soc_dai (device data)
+-> struct snd_soc_dai_driver (driver data)
struct snd_soc_device ---> deleted
This now allows ASoC to be more tightly aligned with the Linux driver model and
also means that every ASoC codec, platform and (platform) DAI is a kernel
device. ASoC component private data is now stored as device private data.
The ASoC sound card struct snd_soc_card has also been updated to store lists
of it's components rather than a pointer to a codec and platform. The PCM
runtime struct soc_pcm_runtime now has pointers to all its components.
This patch adds DAPM support for ASoC multi-component and removes struct
snd_soc_socdev from DAPM core. All DAPM calls are now made on a card, codec
or runtime PCM level basis rather than using snd_soc_socdev.
Other notable multi-component changes:-
* Stream operations now de-reference less structures.
* close_delayed work() now runs on a DAI basis rather than looping all DAIs
in a card.
* PM suspend()/resume() operations can now handle N CODECs and Platforms
per sound card.
* Added soc_bind_dai_link() to bind the component devices to the sound card.
* Added soc_dai_link_probe() and soc_dai_link_remove() to probe and remove
DAI link components.
* sysfs entries can now be registered per component per card.
* snd_soc_new_pcms() functionailty rolled into dai_link_probe().
* snd_soc_register_codec() now does all the codec list and mutex init.
This patch changes the probe() and remove() of the CODEC drivers as follows:-
o Make CODEC driver a platform driver
o Moved all struct snd_soc_codec list, mutex, etc initialiasation to core.
o Removed all static codec pointers (drivers now support > 1 codec dev)
o snd_soc_register_pcms() now done by core.
o snd_soc_register_dai() folded into snd_soc_register_codec().
CS4270 portions:
Acked-by: Timur Tabi <timur@freescale.com>
Some TLV320aic23 and Cirrus platform fixes.
Signed-off-by: Ryan Mallon <ryan@bluewatersys.com>
TI CODEC and OMAP fixes
Signed-off-by: Peter Ujfalusi <peter.ujfalusi@nokia.com>
Signed-off-by: Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>
Signed-off-by: Jarkko Nikula <jhnikula@gmail.com>
Samsung platform and misc fixes :-
Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
Signed-off-by: Joonyoung Shim <jy0922.shim@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
Reviewed-by: Jassi Brar <jassi.brar@samsung.com>
Signed-off-by: Seungwhan Youn <sw.youn@samsung.com>
MPC8610 and PPC fixes.
Signed-off-by: Timur Tabi <timur@freescale.com>
i.MX fixes and some core fixes.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
J4740 platform fixes:-
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
CC: Tony Lindgren <tony@atomide.com>
CC: Nicolas Ferre <nicolas.ferre@atmel.com>
CC: Kevin Hilman <khilman@deeprootsystems.com>
CC: Sascha Hauer <s.hauer@pengutronix.de>
CC: Atsushi Nemoto <anemo@mba.ocn.ne.jp>
CC: Kuninori Morimoto <morimoto.kuninori@renesas.com>
CC: Daniel Gloeckner <dg@emlix.com>
CC: Manuel Lauss <mano@roarinelk.homelinux.net>
CC: Mike Frysinger <vapier.adi@gmail.com>
CC: Arnaud Patard <apatard@mandriva.com>
CC: Wan ZongShun <mcuos.com@gmail.com>
Acked-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Signed-off-by: Liam Girdwood <lrg@slimlogic.co.uk>
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c
index 762c1b8..64f6591 100644
--- a/sound/soc/fsl/fsl_ssi.c
+++ b/sound/soc/fsl/fsl_ssi.c
@@ -3,10 +3,11 @@
*
* Author: Timur Tabi <timur@freescale.com>
*
- * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed
- * under the terms of the GNU General Public License version 2. This
- * program is licensed "as is" without any warranty of any kind, whether
- * express or implied.
+ * Copyright 2007-2010 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
*/
#include <linux/init.h>
@@ -15,6 +16,7 @@
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/slab.h>
+#include <linux/of_platform.h>
#include <sound/core.h>
#include <sound/pcm.h>
@@ -71,33 +73,31 @@
/**
* fsl_ssi_private: per-SSI private data
*
- * @name: short name for this device ("SSI0", "SSI1", etc)
* @ssi: pointer to the SSI's registers
* @ssi_phys: physical address of the SSI registers
* @irq: IRQ of this SSI
* @first_stream: pointer to the stream that was opened first
* @second_stream: pointer to second stream
- * @dev: struct device pointer
* @playback: the number of playback streams opened
* @capture: the number of capture streams opened
* @asynchronous: 0=synchronous mode, 1=asynchronous mode
* @cpu_dai: the CPU DAI for this device
* @dev_attr: the sysfs device attribute structure
* @stats: SSI statistics
+ * @name: name for this device
*/
struct fsl_ssi_private {
- char name[8];
struct ccsr_ssi __iomem *ssi;
dma_addr_t ssi_phys;
unsigned int irq;
struct snd_pcm_substream *first_stream;
struct snd_pcm_substream *second_stream;
- struct device *dev;
unsigned int playback;
unsigned int capture;
int asynchronous;
- struct snd_soc_dai cpu_dai;
+ struct snd_soc_dai_driver cpu_dai_drv;
struct device_attribute dev_attr;
+ struct platform_device *pdev;
struct {
unsigned int rfrc;
@@ -122,6 +122,8 @@
unsigned int tfe1;
unsigned int tfe0;
} stats;
+
+ char name[1];
};
/**
@@ -280,7 +282,7 @@
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
+ struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai);
/*
* If this is the first stream opened, then request the IRQ
@@ -290,6 +292,7 @@
struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
int ret;
+ /* The 'name' should not have any slashes in it. */
ret = request_irq(ssi_private->irq, fsl_ssi_isr, 0,
ssi_private->name, ssi_private);
if (ret < 0) {
@@ -422,7 +425,7 @@
static int fsl_ssi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *cpu_dai)
{
- struct fsl_ssi_private *ssi_private = cpu_dai->private_data;
+ struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai);
if (substream == ssi_private->first_stream) {
struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
@@ -458,7 +461,7 @@
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
+ struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai);
struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
switch (cmd) {
@@ -497,7 +500,7 @@
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data;
+ struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
ssi_private->playback--;
@@ -523,56 +526,15 @@
}
}
-/**
- * fsl_ssi_set_sysclk: set the clock frequency and direction
- *
- * This function is called by the machine driver to tell us what the clock
- * frequency and direction are.
- *
- * Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN),
- * and we don't care about the frequency. Return an error if the direction
- * is not SND_SOC_CLOCK_IN.
- *
- * @clk_id: reserved, should be zero
- * @freq: the frequency of the given clock ID, currently ignored
- * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master)
- */
-static int fsl_ssi_set_sysclk(struct snd_soc_dai *cpu_dai,
- int clk_id, unsigned int freq, int dir)
-{
-
- return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL;
-}
-
-/**
- * fsl_ssi_set_fmt: set the serial format.
- *
- * This function is called by the machine driver to tell us what serial
- * format to use.
- *
- * Currently, we only support I2S mode. Return an error if the format is
- * not SND_SOC_DAIFMT_I2S.
- *
- * @format: one of SND_SOC_DAIFMT_xxx
- */
-static int fsl_ssi_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format)
-{
- return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL;
-}
-
-/**
- * fsl_ssi_dai_template: template CPU DAI for the SSI
- */
static struct snd_soc_dai_ops fsl_ssi_dai_ops = {
.startup = fsl_ssi_startup,
.hw_params = fsl_ssi_hw_params,
.shutdown = fsl_ssi_shutdown,
.trigger = fsl_ssi_trigger,
- .set_sysclk = fsl_ssi_set_sysclk,
- .set_fmt = fsl_ssi_set_fmt,
};
-static struct snd_soc_dai fsl_ssi_dai_template = {
+/* Template for the CPU dai driver structure */
+static struct snd_soc_dai_driver fsl_ssi_dai_template = {
.playback = {
/* The SSI does not support monaural audio. */
.channels_min = 2,
@@ -640,95 +602,176 @@
}
/**
- * fsl_ssi_create_dai: create a snd_soc_dai structure
- *
- * This function is called by the machine driver to create a snd_soc_dai
- * structure. The function creates an ssi_private object, which contains
- * the snd_soc_dai. It also creates the sysfs statistics device.
+ * Make every character in a string lower-case
*/
-struct snd_soc_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info)
+static void make_lowercase(char *s)
{
- struct snd_soc_dai *fsl_ssi_dai;
+ char *p = s;
+ char c;
+
+ while ((c = *p)) {
+ if ((c >= 'A') && (c <= 'Z'))
+ *p = c + ('a' - 'A');
+ p++;
+ }
+}
+
+static int __devinit fsl_ssi_probe(struct of_device *of_dev,
+ const struct of_device_id *match)
+{
struct fsl_ssi_private *ssi_private;
int ret = 0;
struct device_attribute *dev_attr;
+ struct device_node *np = of_dev->dev.of_node;
+ const char *p, *sprop;
+ struct resource res;
+ char name[64];
- ssi_private = kzalloc(sizeof(struct fsl_ssi_private), GFP_KERNEL);
- if (!ssi_private) {
- dev_err(ssi_info->dev, "could not allocate DAI object\n");
- return NULL;
+ /* We are only interested in SSIs with a codec phandle in them, so let's
+ * make sure this SSI has one.
+ */
+ if (!of_get_property(np, "codec-handle", NULL))
+ return -ENODEV;
+
+ /* We only support the SSI in "I2S Slave" mode */
+ sprop = of_get_property(np, "fsl,mode", NULL);
+ if (!sprop || strcmp(sprop, "i2s-slave")) {
+ dev_notice(&of_dev->dev, "mode %s is unsupported\n", sprop);
+ return -ENODEV;
}
- memcpy(&ssi_private->cpu_dai, &fsl_ssi_dai_template,
- sizeof(struct snd_soc_dai));
- fsl_ssi_dai = &ssi_private->cpu_dai;
- dev_attr = &ssi_private->dev_attr;
+ /* The DAI name is the last part of the full name of the node. */
+ p = strrchr(np->full_name, '/') + 1;
+ ssi_private = kzalloc(sizeof(struct fsl_ssi_private) + strlen(p),
+ GFP_KERNEL);
+ if (!ssi_private) {
+ dev_err(&of_dev->dev, "could not allocate DAI object\n");
+ return -ENOMEM;
+ }
- sprintf(ssi_private->name, "ssi%u", (u8) ssi_info->id);
- ssi_private->ssi = ssi_info->ssi;
- ssi_private->ssi_phys = ssi_info->ssi_phys;
- ssi_private->irq = ssi_info->irq;
- ssi_private->dev = ssi_info->dev;
- ssi_private->asynchronous = ssi_info->asynchronous;
+ strcpy(ssi_private->name, p);
- dev_set_drvdata(ssi_private->dev, fsl_ssi_dai);
+ /* Initialize this copy of the CPU DAI driver structure */
+ memcpy(&ssi_private->cpu_dai_drv, &fsl_ssi_dai_template,
+ sizeof(fsl_ssi_dai_template));
+ ssi_private->cpu_dai_drv.name = ssi_private->name;
+
+ /* Get the addresses and IRQ */
+ ret = of_address_to_resource(np, 0, &res);
+ if (ret) {
+ dev_err(&of_dev->dev, "could not determine device resources\n");
+ kfree(ssi_private);
+ return ret;
+ }
+ ssi_private->ssi = ioremap(res.start, 1 + res.end - res.start);
+ ssi_private->ssi_phys = res.start;
+ ssi_private->irq = irq_of_parse_and_map(np, 0);
+
+ /* Are the RX and the TX clocks locked? */
+ if (of_find_property(np, "fsl,ssi-asynchronous", NULL))
+ ssi_private->asynchronous = 1;
+ else
+ ssi_private->cpu_dai_drv.symmetric_rates = 1;
/* Initialize the the device_attribute structure */
- dev_attr->attr.name = "ssi-stats";
+ dev_attr = &ssi_private->dev_attr;
+ dev_attr->attr.name = "statistics";
dev_attr->attr.mode = S_IRUGO;
dev_attr->show = fsl_sysfs_ssi_show;
- ret = device_create_file(ssi_private->dev, dev_attr);
+ ret = device_create_file(&of_dev->dev, dev_attr);
if (ret) {
- dev_err(ssi_info->dev, "could not create sysfs %s file\n",
+ dev_err(&of_dev->dev, "could not create sysfs %s file\n",
ssi_private->dev_attr.attr.name);
- kfree(fsl_ssi_dai);
- return NULL;
+ kfree(ssi_private);
+ return ret;
}
- fsl_ssi_dai->private_data = ssi_private;
- fsl_ssi_dai->name = ssi_private->name;
- fsl_ssi_dai->id = ssi_info->id;
- fsl_ssi_dai->dev = ssi_info->dev;
- fsl_ssi_dai->symmetric_rates = 1;
+ /* Register with ASoC */
+ dev_set_drvdata(&of_dev->dev, ssi_private);
- ret = snd_soc_register_dai(fsl_ssi_dai);
+ ret = snd_soc_register_dai(&of_dev->dev, &ssi_private->cpu_dai_drv);
if (ret != 0) {
- dev_err(ssi_info->dev, "failed to register DAI: %d\n", ret);
- kfree(fsl_ssi_dai);
- return NULL;
+ dev_err(&of_dev->dev, "failed to register DAI: %d\n", ret);
+ kfree(ssi_private);
+ return ret;
}
- return fsl_ssi_dai;
+ /* Trigger the machine driver's probe function. The platform driver
+ * name of the machine driver is taken from the /model property of the
+ * device tree. We also pass the address of the CPU DAI driver
+ * structure.
+ */
+ sprop = of_get_property(of_find_node_by_path("/"), "model", NULL);
+ /* Sometimes the model name has a "fsl," prefix, so we strip that. */
+ p = strrchr(sprop, ',');
+ if (p)
+ sprop = p + 1;
+ snprintf(name, sizeof(name), "snd-soc-%s", sprop);
+ make_lowercase(name);
+
+ ssi_private->pdev =
+ platform_device_register_data(&of_dev->dev, name, 0, NULL, 0);
+ if (IS_ERR(ssi_private->pdev)) {
+ ret = PTR_ERR(ssi_private->pdev);
+ dev_err(&of_dev->dev, "failed to register platform: %d\n", ret);
+ kfree(ssi_private);
+ return ret;
+ }
+
+ return 0;
}
-EXPORT_SYMBOL_GPL(fsl_ssi_create_dai);
/**
* fsl_ssi_destroy_dai: destroy the snd_soc_dai object
*
- * This function undoes the operations of fsl_ssi_create_dai()
+ * This function undoes the operations of fsl_ssi_probe()
*/
-void fsl_ssi_destroy_dai(struct snd_soc_dai *fsl_ssi_dai)
+static int fsl_ssi_remove(struct of_device *of_dev)
{
- struct fsl_ssi_private *ssi_private =
- container_of(fsl_ssi_dai, struct fsl_ssi_private, cpu_dai);
+ struct fsl_ssi_private *ssi_private = dev_get_drvdata(&of_dev->dev);
- device_remove_file(ssi_private->dev, &ssi_private->dev_attr);
-
- snd_soc_unregister_dai(&ssi_private->cpu_dai);
+ platform_device_unregister(ssi_private->pdev);
+ snd_soc_unregister_dai(&of_dev->dev);
+ device_remove_file(&of_dev->dev, &ssi_private->dev_attr);
kfree(ssi_private);
+ dev_set_drvdata(&of_dev->dev, NULL);
+
+ return 0;
}
-EXPORT_SYMBOL_GPL(fsl_ssi_destroy_dai);
+
+static const struct of_device_id fsl_ssi_ids[] = {
+ { .compatible = "fsl,mpc8610-ssi", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_ssi_ids);
+
+static struct of_platform_driver fsl_ssi_driver = {
+ .driver = {
+ .name = "fsl-ssi-dai",
+ .owner = THIS_MODULE,
+ .of_match_table = fsl_ssi_ids,
+ },
+ .probe = fsl_ssi_probe,
+ .remove = fsl_ssi_remove,
+};
static int __init fsl_ssi_init(void)
{
printk(KERN_INFO "Freescale Synchronous Serial Interface (SSI) ASoC Driver\n");
- return 0;
+ return of_register_platform_driver(&fsl_ssi_driver);
}
+
+static void __exit fsl_ssi_exit(void)
+{
+ of_unregister_platform_driver(&fsl_ssi_driver);
+}
+
module_init(fsl_ssi_init);
+module_exit(fsl_ssi_exit);
MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
MODULE_DESCRIPTION("Freescale Synchronous Serial Interface (SSI) ASoC Driver");
-MODULE_LICENSE("GPL");
+MODULE_LICENSE("GPL v2");