diff options
Diffstat (limited to 'sound/soc/fsl/fsl_ssi.c')
-rw-r--r-- | sound/soc/fsl/fsl_ssi.c | 249 |
1 files changed, 146 insertions, 103 deletions
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 762c1b8e8e4e..64f65910a7d7 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 @@ struct fsl_ssi_private { unsigned int tfe1; unsigned int tfe0; } stats; + + char name[1]; }; /** @@ -280,7 +282,7 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, 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 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, 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_startup(struct snd_pcm_substream *substream, 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 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd, 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 @@ static void fsl_ssi_shutdown(struct snd_pcm_substream *substream, 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 @@ static void fsl_ssi_shutdown(struct snd_pcm_substream *substream, } } -/** - * 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 @@ static ssize_t fsl_sysfs_ssi_show(struct device *dev, } /** - * 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) +{ + 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 snd_soc_dai *fsl_ssi_dai; 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); + /* 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; + } + + /* 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(ssi_info->dev, "could not allocate DAI object\n"); - return NULL; + dev_err(&of_dev->dev, "could not allocate DAI object\n"); + return -ENOMEM; } - 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; + strcpy(ssi_private->name, p); - 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; + /* 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); - dev_set_drvdata(ssi_private->dev, fsl_ssi_dai); + /* 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; + } + + /* 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 fsl_ssi_dai; + 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"); |