diff options
| author | Meizu OpenSource <patchwork@meizu.com> | 2016-08-15 10:19:42 +0800 |
|---|---|---|
| committer | Meizu OpenSource <patchwork@meizu.com> | 2016-08-15 10:19:42 +0800 |
| commit | d2e1446d81725c351dc73a03b397ce043fb18452 (patch) | |
| tree | 4dbc616b7f92aea39cd697a9084205ddb805e344 /sound/soc/mxs | |
first commit
Diffstat (limited to 'sound/soc/mxs')
| -rw-r--r-- | sound/soc/mxs/Kconfig | 20 | ||||
| -rw-r--r-- | sound/soc/mxs/Makefile | 10 | ||||
| -rw-r--r-- | sound/soc/mxs/mxs-pcm.c | 89 | ||||
| -rw-r--r-- | sound/soc/mxs/mxs-pcm.h | 32 | ||||
| -rw-r--r-- | sound/soc/mxs/mxs-saif.c | 823 | ||||
| -rw-r--r-- | sound/soc/mxs/mxs-saif.h | 133 | ||||
| -rw-r--r-- | sound/soc/mxs/mxs-sgtl5000.c | 211 |
7 files changed, 1318 insertions, 0 deletions
diff --git a/sound/soc/mxs/Kconfig b/sound/soc/mxs/Kconfig new file mode 100644 index 000000000..78d321cbe --- /dev/null +++ b/sound/soc/mxs/Kconfig @@ -0,0 +1,20 @@ +menuconfig SND_MXS_SOC + tristate "SoC Audio for Freescale MXS CPUs" + depends on ARCH_MXS + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for codecs attached to + the MXS SAIF interface. + + +if SND_MXS_SOC + +config SND_SOC_MXS_SGTL5000 + tristate "SoC Audio support for MXS boards with sgtl5000" + depends on I2C + select SND_SOC_SGTL5000 + help + Say Y if you want to add support for SoC audio on an MXS board with + a sgtl5000 codec. + +endif # SND_MXS_SOC diff --git a/sound/soc/mxs/Makefile b/sound/soc/mxs/Makefile new file mode 100644 index 000000000..565b5b51e --- /dev/null +++ b/sound/soc/mxs/Makefile @@ -0,0 +1,10 @@ +# MXS Platform Support +snd-soc-mxs-objs := mxs-saif.o +snd-soc-mxs-pcm-objs := mxs-pcm.o + +obj-$(CONFIG_SND_MXS_SOC) += snd-soc-mxs.o snd-soc-mxs-pcm.o + +# i.MX Machine Support +snd-soc-mxs-sgtl5000-objs := mxs-sgtl5000.o + +obj-$(CONFIG_SND_SOC_MXS_SGTL5000) += snd-soc-mxs-sgtl5000.o diff --git a/sound/soc/mxs/mxs-pcm.c b/sound/soc/mxs/mxs-pcm.c new file mode 100644 index 000000000..b41fffc05 --- /dev/null +++ b/sound/soc/mxs/mxs-pcm.c @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * Based on sound/soc/imx/imx-pcm-dma-mx2.c + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/device.h> +#include <linux/init.h> +#include <linux/module.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +#include "mxs-pcm.h" + +static const struct snd_pcm_hardware snd_mxs_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_HALF_DUPLEX, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 2, + .channels_max = 2, + .period_bytes_min = 32, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 52, + .buffer_bytes_max = 64 * 1024, + .fifo_size = 32, +}; + +static bool filter(struct dma_chan *chan, void *param) +{ + struct mxs_pcm_dma_params *dma_params = param; + + if (!mxs_dma_is_apbx(chan)) + return false; + + if (chan->chan_id != dma_params->chan_num) + return false; + + chan->private = &dma_params->dma_data; + + return true; +} + +static const struct snd_dmaengine_pcm_config mxs_dmaengine_pcm_config = { + .pcm_hardware = &snd_mxs_hardware, + .compat_filter_fn = filter, + .prealloc_buffer_size = 64 * 1024, +}; + +int mxs_pcm_platform_register(struct device *dev) +{ + return snd_dmaengine_pcm_register(dev, &mxs_dmaengine_pcm_config, + SND_DMAENGINE_PCM_FLAG_NO_RESIDUE | + SND_DMAENGINE_PCM_FLAG_NO_DT | + SND_DMAENGINE_PCM_FLAG_COMPAT | + SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX); +} +EXPORT_SYMBOL_GPL(mxs_pcm_platform_register); + +void mxs_pcm_platform_unregister(struct device *dev) +{ + snd_dmaengine_pcm_unregister(dev); +} +EXPORT_SYMBOL_GPL(mxs_pcm_platform_unregister); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/mxs/mxs-pcm.h b/sound/soc/mxs/mxs-pcm.h new file mode 100644 index 000000000..3aa918f9e --- /dev/null +++ b/sound/soc/mxs/mxs-pcm.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _MXS_PCM_H +#define _MXS_PCM_H + +#include <linux/fsl/mxs-dma.h> + +struct mxs_pcm_dma_params { + struct mxs_dma_data dma_data; + int chan_num; +}; + +int mxs_pcm_platform_register(struct device *dev); +void mxs_pcm_platform_unregister(struct device *dev); + +#endif diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c new file mode 100644 index 000000000..d31dc52fa --- /dev/null +++ b/sound/soc/mxs/mxs-saif.c @@ -0,0 +1,823 @@ +/* + * Copyright 2011 Freescale Semiconductor, Inc. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <linux/fsl/mxs-dma.h> +#include <linux/pinctrl/consumer.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <asm/mach-types.h> + +#include "mxs-saif.h" + +#define MXS_SET_ADDR 0x4 +#define MXS_CLR_ADDR 0x8 + +static struct mxs_saif *mxs_saif[2]; + +/* + * SAIF is a little different with other normal SOC DAIs on clock using. + * + * For MXS, two SAIF modules are instantiated on-chip. + * Each SAIF has a set of clock pins and can be operating in master + * mode simultaneously if they are connected to different off-chip codecs. + * Also, one of the two SAIFs can master or drive the clock pins while the + * other SAIF, in slave mode, receives clocking from the master SAIF. + * This also means that both SAIFs must operate at the same sample rate. + * + * We abstract this as each saif has a master, the master could be + * himself or other saifs. In the generic saif driver, saif does not need + * to know the different clkmux. Saif only needs to know who is his master + * and operating his master to generate the proper clock rate for him. + * The master id is provided in mach-specific layer according to different + * clkmux setting. + */ + +static int mxs_saif_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + + switch (clk_id) { + case MXS_SAIF_MCLK: + saif->mclk = freq; + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * Since SAIF may work on EXTMASTER mode, IOW, it's working BITCLK&LRCLK + * is provided by other SAIF, we provide a interface here to get its master + * from its master_id. + * Note that the master could be himself. + */ +static inline struct mxs_saif *mxs_saif_get_master(struct mxs_saif * saif) +{ + return mxs_saif[saif->master_id]; +} + +/* + * Set SAIF clock and MCLK + */ +static int mxs_saif_set_clk(struct mxs_saif *saif, + unsigned int mclk, + unsigned int rate) +{ + u32 scr; + int ret; + struct mxs_saif *master_saif; + + dev_dbg(saif->dev, "mclk %d rate %d\n", mclk, rate); + + /* Set master saif to generate proper clock */ + master_saif = mxs_saif_get_master(saif); + if (!master_saif) + return -EINVAL; + + dev_dbg(saif->dev, "master saif%d\n", master_saif->id); + + /* Checking if can playback and capture simutaneously */ + if (master_saif->ongoing && rate != master_saif->cur_rate) { + dev_err(saif->dev, + "can not change clock, master saif%d(rate %d) is ongoing\n", + master_saif->id, master_saif->cur_rate); + return -EINVAL; + } + + scr = __raw_readl(master_saif->base + SAIF_CTRL); + scr &= ~BM_SAIF_CTRL_BITCLK_MULT_RATE; + scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; + + /* + * Set SAIF clock + * + * The SAIF clock should be either 384*fs or 512*fs. + * If MCLK is used, the SAIF clk ratio need to match mclk ratio. + * For 32x mclk, set saif clk as 512*fs. + * For 48x mclk, set saif clk as 384*fs. + * + * If MCLK is not used, we just set saif clk to 512*fs. + */ + clk_prepare_enable(master_saif->clk); + + if (master_saif->mclk_in_use) { + if (mclk % 32 == 0) { + scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; + ret = clk_set_rate(master_saif->clk, 512 * rate); + } else if (mclk % 48 == 0) { + scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE; + ret = clk_set_rate(master_saif->clk, 384 * rate); + } else { + /* SAIF MCLK should be either 32x or 48x */ + clk_disable_unprepare(master_saif->clk); + return -EINVAL; + } + } else { + ret = clk_set_rate(master_saif->clk, 512 * rate); + scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; + } + + clk_disable_unprepare(master_saif->clk); + + if (ret) + return ret; + + master_saif->cur_rate = rate; + + if (!master_saif->mclk_in_use) { + __raw_writel(scr, master_saif->base + SAIF_CTRL); + return 0; + } + + /* + * Program the over-sample rate for MCLK output + * + * The available MCLK range is 32x, 48x... 512x. The rate + * could be from 8kHz to 192kH. + */ + switch (mclk / rate) { + case 32: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(4); + break; + case 64: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(3); + break; + case 128: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(2); + break; + case 256: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(1); + break; + case 512: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(0); + break; + case 48: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(3); + break; + case 96: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(2); + break; + case 192: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(1); + break; + case 384: + scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(0); + break; + default: + return -EINVAL; + } + + __raw_writel(scr, master_saif->base + SAIF_CTRL); + + return 0; +} + +/* + * Put and disable MCLK. + */ +int mxs_saif_put_mclk(unsigned int saif_id) +{ + struct mxs_saif *saif = mxs_saif[saif_id]; + u32 stat; + + if (!saif) + return -EINVAL; + + stat = __raw_readl(saif->base + SAIF_STAT); + if (stat & BM_SAIF_STAT_BUSY) { + dev_err(saif->dev, "error: busy\n"); + return -EBUSY; + } + + clk_disable_unprepare(saif->clk); + + /* disable MCLK output */ + __raw_writel(BM_SAIF_CTRL_CLKGATE, + saif->base + SAIF_CTRL + MXS_SET_ADDR); + __raw_writel(BM_SAIF_CTRL_RUN, + saif->base + SAIF_CTRL + MXS_CLR_ADDR); + + saif->mclk_in_use = 0; + return 0; +} +EXPORT_SYMBOL_GPL(mxs_saif_put_mclk); + +/* + * Get MCLK and set clock rate, then enable it + * + * This interface is used for codecs who are using MCLK provided + * by saif. + */ +int mxs_saif_get_mclk(unsigned int saif_id, unsigned int mclk, + unsigned int rate) +{ + struct mxs_saif *saif = mxs_saif[saif_id]; + u32 stat; + int ret; + struct mxs_saif *master_saif; + + if (!saif) + return -EINVAL; + + /* Clear Reset */ + __raw_writel(BM_SAIF_CTRL_SFTRST, + saif->base + SAIF_CTRL + MXS_CLR_ADDR); + + /* FIXME: need clear clk gate for register r/w */ + __raw_writel(BM_SAIF_CTRL_CLKGATE, + saif->base + SAIF_CTRL + MXS_CLR_ADDR); + + master_saif = mxs_saif_get_master(saif); + if (saif != master_saif) { + dev_err(saif->dev, "can not get mclk from a non-master saif\n"); + return -EINVAL; + } + + stat = __raw_readl(saif->base + SAIF_STAT); + if (stat & BM_SAIF_STAT_BUSY) { + dev_err(saif->dev, "error: busy\n"); + return -EBUSY; + } + + saif->mclk_in_use = 1; + ret = mxs_saif_set_clk(saif, mclk, rate); + if (ret) + return ret; + + ret = clk_prepare_enable(saif->clk); + if (ret) + return ret; + + /* enable MCLK output */ + __raw_writel(BM_SAIF_CTRL_RUN, + saif->base + SAIF_CTRL + MXS_SET_ADDR); + + return 0; +} +EXPORT_SYMBOL_GPL(mxs_saif_get_mclk); + +/* + * SAIF DAI format configuration. + * Should only be called when port is inactive. + */ +static int mxs_saif_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + u32 scr, stat; + u32 scr0; + struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + + stat = __raw_readl(saif->base + SAIF_STAT); + if (stat & BM_SAIF_STAT_BUSY) { + dev_err(cpu_dai->dev, "error: busy\n"); + return -EBUSY; + } + + scr0 = __raw_readl(saif->base + SAIF_CTRL); + scr0 = scr0 & ~BM_SAIF_CTRL_BITCLK_EDGE & ~BM_SAIF_CTRL_LRCLK_POLARITY \ + & ~BM_SAIF_CTRL_JUSTIFY & ~BM_SAIF_CTRL_DELAY; + scr = 0; + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* data frame low 1clk before data */ + scr |= BM_SAIF_CTRL_DELAY; + scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY; + break; + case SND_SOC_DAIFMT_LEFT_J: + /* data frame high with data */ + scr &= ~BM_SAIF_CTRL_DELAY; + scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY; + scr &= ~BM_SAIF_CTRL_JUSTIFY; + break; + default: + return -EINVAL; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + scr |= BM_SAIF_CTRL_BITCLK_EDGE; + scr |= BM_SAIF_CTRL_LRCLK_POLARITY; + break; + case SND_SOC_DAIFMT_IB_NF: + scr |= BM_SAIF_CTRL_BITCLK_EDGE; + scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY; + break; + case SND_SOC_DAIFMT_NB_IF: + scr &= ~BM_SAIF_CTRL_BITCLK_EDGE; + scr |= BM_SAIF_CTRL_LRCLK_POLARITY; + break; + case SND_SOC_DAIFMT_NB_NF: + scr &= ~BM_SAIF_CTRL_BITCLK_EDGE; + scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY; + break; + } + + /* + * Note: We simply just support master mode since SAIF TX can only + * work as master. + * Here the master is relative to codec side. + * Saif internally could be slave when working on EXTMASTER mode. + * We just hide this to machine driver. + */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + if (saif->id == saif->master_id) + scr &= ~BM_SAIF_CTRL_SLAVE_MODE; + else + scr |= BM_SAIF_CTRL_SLAVE_MODE; + + __raw_writel(scr | scr0, saif->base + SAIF_CTRL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int mxs_saif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + + /* clear error status to 0 for each re-open */ + saif->fifo_underrun = 0; + saif->fifo_overrun = 0; + + /* Clear Reset for normal operations */ + __raw_writel(BM_SAIF_CTRL_SFTRST, + saif->base + SAIF_CTRL + MXS_CLR_ADDR); + + /* clear clock gate */ + __raw_writel(BM_SAIF_CTRL_CLKGATE, + saif->base + SAIF_CTRL + MXS_CLR_ADDR); + + return 0; +} + +/* + * Should only be called when port is inactive. + * although can be called multiple times by upper layers. + */ +static int mxs_saif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + struct mxs_saif *master_saif; + u32 scr, stat; + int ret; + + master_saif = mxs_saif_get_master(saif); + if (!master_saif) + return -EINVAL; + + /* mclk should already be set */ + if (!saif->mclk && saif->mclk_in_use) { + dev_err(cpu_dai->dev, "set mclk first\n"); + return -EINVAL; + } + + stat = __raw_readl(saif->base + SAIF_STAT); + if (stat & BM_SAIF_STAT_BUSY) { + dev_err(cpu_dai->dev, "error: busy\n"); + return -EBUSY; + } + + /* + * Set saif clk based on sample rate. + * If mclk is used, we also set mclk, if not, saif->mclk is + * default 0, means not used. + */ + ret = mxs_saif_set_clk(saif, saif->mclk, params_rate(params)); + if (ret) { + dev_err(cpu_dai->dev, "unable to get proper clk\n"); + return ret; + } + + /* prepare clk in hw_param, enable in trigger */ + clk_prepare(saif->clk); + if (saif != master_saif) { + /* + * Set an initial clock rate for the saif internal logic to work + * properly. This is important when working in EXTMASTER mode + * that uses the other saif's BITCLK&LRCLK but it still needs a + * basic clock which should be fast enough for the internal + * logic. + */ + clk_enable(saif->clk); + ret = clk_set_rate(saif->clk, 24000000); + clk_disable(saif->clk); + if (ret) + return ret; + + clk_prepare(master_saif->clk); + } + + scr = __raw_readl(saif->base + SAIF_CTRL); + + scr &= ~BM_SAIF_CTRL_WORD_LENGTH; + scr &= ~BM_SAIF_CTRL_BITCLK_48XFS_ENABLE; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + scr |= BF_SAIF_CTRL_WORD_LENGTH(0); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + scr |= BF_SAIF_CTRL_WORD_LENGTH(4); + scr |= BM_SAIF_CTRL_BITCLK_48XFS_ENABLE; + break; + case SNDRV_PCM_FORMAT_S24_LE: + scr |= BF_SAIF_CTRL_WORD_LENGTH(8); + scr |= BM_SAIF_CTRL_BITCLK_48XFS_ENABLE; + break; + default: + return -EINVAL; + } + + /* Tx/Rx config */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* enable TX mode */ + scr &= ~BM_SAIF_CTRL_READ_MODE; + } else { + /* enable RX mode */ + scr |= BM_SAIF_CTRL_READ_MODE; + } + + __raw_writel(scr, saif->base + SAIF_CTRL); + return 0; +} + +static int mxs_saif_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + + /* enable FIFO error irqs */ + __raw_writel(BM_SAIF_CTRL_FIFO_ERROR_IRQ_EN, + saif->base + SAIF_CTRL + MXS_SET_ADDR); + + return 0; +} + +static int mxs_saif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + struct mxs_saif *master_saif; + u32 delay; + + master_saif = mxs_saif_get_master(saif); + if (!master_saif) + return -EINVAL; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dev_dbg(cpu_dai->dev, "start\n"); + + clk_enable(master_saif->clk); + if (!master_saif->mclk_in_use) + __raw_writel(BM_SAIF_CTRL_RUN, + master_saif->base + SAIF_CTRL + MXS_SET_ADDR); + + /* + * If the saif's master is not himself, we also need to enable + * itself clk for its internal basic logic to work. + */ + if (saif != master_saif) { + clk_enable(saif->clk); + __raw_writel(BM_SAIF_CTRL_RUN, + saif->base + SAIF_CTRL + MXS_SET_ADDR); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* + * write data to saif data register to trigger + * the transfer. + * For 24-bit format the 32-bit FIFO register stores + * only one channel, so we need to write twice. + * This is also safe for the other non 24-bit formats. + */ + __raw_writel(0, saif->base + SAIF_DATA); + __raw_writel(0, saif->base + SAIF_DATA); + } else { + /* + * read data from saif data register to trigger + * the receive. + * For 24-bit format the 32-bit FIFO register stores + * only one channel, so we need to read twice. + * This is also safe for the other non 24-bit formats. + */ + __raw_readl(saif->base + SAIF_DATA); + __raw_readl(saif->base + SAIF_DATA); + } + + master_saif->ongoing = 1; + + dev_dbg(saif->dev, "CTRL 0x%x STAT 0x%x\n", + __raw_readl(saif->base + SAIF_CTRL), + __raw_readl(saif->base + SAIF_STAT)); + + dev_dbg(master_saif->dev, "CTRL 0x%x STAT 0x%x\n", + __raw_readl(master_saif->base + SAIF_CTRL), + __raw_readl(master_saif->base + SAIF_STAT)); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dev_dbg(cpu_dai->dev, "stop\n"); + + /* wait a while for the current sample to complete */ + delay = USEC_PER_SEC / master_saif->cur_rate; + + if (!master_saif->mclk_in_use) { + __raw_writel(BM_SAIF_CTRL_RUN, + master_saif->base + SAIF_CTRL + MXS_CLR_ADDR); + udelay(delay); + } + clk_disable(master_saif->clk); + + if (saif != master_saif) { + __raw_writel(BM_SAIF_CTRL_RUN, + saif->base + SAIF_CTRL + MXS_CLR_ADDR); + udelay(delay); + clk_disable(saif->clk); + } + + master_saif->ongoing = 0; + + break; + default: + return -EINVAL; + } + + return 0; +} + +#define MXS_SAIF_RATES SNDRV_PCM_RATE_8000_192000 +#define MXS_SAIF_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops mxs_saif_dai_ops = { + .startup = mxs_saif_startup, + .trigger = mxs_saif_trigger, + .prepare = mxs_saif_prepare, + .hw_params = mxs_saif_hw_params, + .set_sysclk = mxs_saif_set_dai_sysclk, + .set_fmt = mxs_saif_set_dai_fmt, +}; + +static int mxs_saif_dai_probe(struct snd_soc_dai *dai) +{ + struct mxs_saif *saif = dev_get_drvdata(dai->dev); + + snd_soc_dai_set_drvdata(dai, saif); + dai->playback_dma_data = &saif->dma_param; + dai->capture_dma_data = &saif->dma_param; + + return 0; +} + +static struct snd_soc_dai_driver mxs_saif_dai = { + .name = "mxs-saif", + .probe = mxs_saif_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = MXS_SAIF_RATES, + .formats = MXS_SAIF_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = MXS_SAIF_RATES, + .formats = MXS_SAIF_FORMATS, + }, + .ops = &mxs_saif_dai_ops, +}; + +static const struct snd_soc_component_driver mxs_saif_component = { + .name = "mxs-saif", +}; + +static irqreturn_t mxs_saif_irq(int irq, void *dev_id) +{ + struct mxs_saif *saif = dev_id; + unsigned int stat; + + stat = __raw_readl(saif->base + SAIF_STAT); + if (!(stat & (BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ | + BM_SAIF_STAT_FIFO_OVERFLOW_IRQ))) + return IRQ_NONE; + + if (stat & BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ) { + dev_dbg(saif->dev, "underrun!!! %d\n", ++saif->fifo_underrun); + __raw_writel(BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ, + saif->base + SAIF_STAT + MXS_CLR_ADDR); + } + + if (stat & BM_SAIF_STAT_FIFO_OVERFLOW_IRQ) { + dev_dbg(saif->dev, "overrun!!! %d\n", ++saif->fifo_overrun); + __raw_writel(BM_SAIF_STAT_FIFO_OVERFLOW_IRQ, + saif->base + SAIF_STAT + MXS_CLR_ADDR); + } + + dev_dbg(saif->dev, "SAIF_CTRL %x SAIF_STAT %x\n", + __raw_readl(saif->base + SAIF_CTRL), + __raw_readl(saif->base + SAIF_STAT)); + + return IRQ_HANDLED; +} + +static int mxs_saif_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct resource *iores, *dmares; + struct mxs_saif *saif; + struct pinctrl *pinctrl; + int ret = 0; + struct device_node *master; + + if (!np) + return -EINVAL; + + saif = devm_kzalloc(&pdev->dev, sizeof(*saif), GFP_KERNEL); + if (!saif) + return -ENOMEM; + + ret = of_alias_get_id(np, "saif"); + if (ret < 0) + return ret; + else + saif->id = ret; + + /* + * If there is no "fsl,saif-master" phandle, it's a saif + * master. Otherwise, it's a slave and its phandle points + * to the master. + */ + master = of_parse_phandle(np, "fsl,saif-master", 0); + if (!master) { + saif->master_id = saif->id; + } else { + ret = of_alias_get_id(master, "saif"); + if (ret < 0) + return ret; + else + saif->master_id = ret; + } + + if (saif->master_id >= ARRAY_SIZE(mxs_saif)) { + dev_err(&pdev->dev, "get wrong master id\n"); + return -EINVAL; + } + + mxs_saif[saif->id] = saif; + + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) { + ret = PTR_ERR(pinctrl); + return ret; + } + + saif->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(saif->clk)) { + ret = PTR_ERR(saif->clk); + dev_err(&pdev->dev, "Cannot get the clock: %d\n", + ret); + return ret; + } + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + saif->base = devm_ioremap_resource(&pdev->dev, iores); + if (IS_ERR(saif->base)) + return PTR_ERR(saif->base); + + dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dmares) { + /* + * TODO: This is a temporary solution and should be changed + * to use generic DMA binding later when the helplers get in. + */ + ret = of_property_read_u32(np, "fsl,saif-dma-channel", + &saif->dma_param.chan_num); + if (ret) { + dev_err(&pdev->dev, "failed to get dma channel\n"); + return ret; + } + } else { + saif->dma_param.chan_num = dmares->start; + } + + saif->irq = platform_get_irq(pdev, 0); + if (saif->irq < 0) { + ret = saif->irq; + dev_err(&pdev->dev, "failed to get irq resource: %d\n", + ret); + return ret; + } + + saif->dev = &pdev->dev; + ret = devm_request_irq(&pdev->dev, saif->irq, mxs_saif_irq, 0, + "mxs-saif", saif); + if (ret) { + dev_err(&pdev->dev, "failed to request irq\n"); + return ret; + } + + saif->dma_param.dma_data.chan_irq = platform_get_irq(pdev, 1); + if (saif->dma_param.dma_data.chan_irq < 0) { + ret = saif->dma_param.dma_data.chan_irq; + dev_err(&pdev->dev, "failed to get dma irq resource: %d\n", + ret); + return ret; + } + + platform_set_drvdata(pdev, saif); + + ret = snd_soc_register_component(&pdev->dev, &mxs_saif_component, + &mxs_saif_dai, 1); + if (ret) { + dev_err(&pdev->dev, "register DAI failed\n"); + return ret; + } + + ret = mxs_pcm_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "register PCM failed: %d\n", ret); + goto failed_pdev_alloc; + } + + return 0; + +failed_pdev_alloc: + snd_soc_unregister_component(&pdev->dev); + + return ret; +} + +static int mxs_saif_remove(struct platform_device *pdev) +{ + mxs_pcm_platform_unregister(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + + return 0; +} + +static const struct of_device_id mxs_saif_dt_ids[] = { + { .compatible = "fsl,imx28-saif", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_saif_dt_ids); + +static struct platform_driver mxs_saif_driver = { + .probe = mxs_saif_probe, + .remove = mxs_saif_remove, + + .driver = { + .name = "mxs-saif", + .owner = THIS_MODULE, + .of_match_table = mxs_saif_dt_ids, + }, +}; + +module_platform_driver(mxs_saif_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXS ASoC SAIF driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mxs-saif"); diff --git a/sound/soc/mxs/mxs-saif.h b/sound/soc/mxs/mxs-saif.h new file mode 100644 index 000000000..3cb342e5b --- /dev/null +++ b/sound/soc/mxs/mxs-saif.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#ifndef _MXS_SAIF_H +#define _MXS_SAIF_H + +#define SAIF_CTRL 0x0 +#define SAIF_STAT 0x10 +#define SAIF_DATA 0x20 +#define SAIF_VERSION 0X30 + +/* SAIF_CTRL */ +#define BM_SAIF_CTRL_SFTRST 0x80000000 +#define BM_SAIF_CTRL_CLKGATE 0x40000000 +#define BP_SAIF_CTRL_BITCLK_MULT_RATE 27 +#define BM_SAIF_CTRL_BITCLK_MULT_RATE 0x38000000 +#define BF_SAIF_CTRL_BITCLK_MULT_RATE(v) \ + (((v) << 27) & BM_SAIF_CTRL_BITCLK_MULT_RATE) +#define BM_SAIF_CTRL_BITCLK_BASE_RATE 0x04000000 +#define BM_SAIF_CTRL_FIFO_ERROR_IRQ_EN 0x02000000 +#define BM_SAIF_CTRL_FIFO_SERVICE_IRQ_EN 0x01000000 +#define BP_SAIF_CTRL_RSRVD2 21 +#define BM_SAIF_CTRL_RSRVD2 0x00E00000 + +#define BP_SAIF_CTRL_DMAWAIT_COUNT 16 +#define BM_SAIF_CTRL_DMAWAIT_COUNT 0x001F0000 +#define BF_SAIF_CTRL_DMAWAIT_COUNT(v) \ + (((v) << 16) & BM_SAIF_CTRL_DMAWAIT_COUNT) +#define BP_SAIF_CTRL_CHANNEL_NUM_SELECT 14 +#define BM_SAIF_CTRL_CHANNEL_NUM_SELECT 0x0000C000 +#define BF_SAIF_CTRL_CHANNEL_NUM_SELECT(v) \ + (((v) << 14) & BM_SAIF_CTRL_CHANNEL_NUM_SELECT) +#define BM_SAIF_CTRL_LRCLK_PULSE 0x00002000 +#define BM_SAIF_CTRL_BIT_ORDER 0x00001000 +#define BM_SAIF_CTRL_DELAY 0x00000800 +#define BM_SAIF_CTRL_JUSTIFY 0x00000400 +#define BM_SAIF_CTRL_LRCLK_POLARITY 0x00000200 +#define BM_SAIF_CTRL_BITCLK_EDGE 0x00000100 +#define BP_SAIF_CTRL_WORD_LENGTH 4 +#define BM_SAIF_CTRL_WORD_LENGTH 0x000000F0 +#define BF_SAIF_CTRL_WORD_LENGTH(v) \ + (((v) << 4) & BM_SAIF_CTRL_WORD_LENGTH) +#define BM_SAIF_CTRL_BITCLK_48XFS_ENABLE 0x00000008 +#define BM_SAIF_CTRL_SLAVE_MODE 0x00000004 +#define BM_SAIF_CTRL_READ_MODE 0x00000002 +#define BM_SAIF_CTRL_RUN 0x00000001 + +/* SAIF_STAT */ +#define BM_SAIF_STAT_PRESENT 0x80000000 +#define BP_SAIF_STAT_RSRVD2 17 +#define BM_SAIF_STAT_RSRVD2 0x7FFE0000 +#define BF_SAIF_STAT_RSRVD2(v) \ + (((v) << 17) & BM_SAIF_STAT_RSRVD2) +#define BM_SAIF_STAT_DMA_PREQ 0x00010000 +#define BP_SAIF_STAT_RSRVD1 7 +#define BM_SAIF_STAT_RSRVD1 0x0000FF80 +#define BF_SAIF_STAT_RSRVD1(v) \ + (((v) << 7) & BM_SAIF_STAT_RSRVD1) + +#define BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ 0x00000040 +#define BM_SAIF_STAT_FIFO_OVERFLOW_IRQ 0x00000020 +#define BM_SAIF_STAT_FIFO_SERVICE_IRQ 0x00000010 +#define BP_SAIF_STAT_RSRVD0 1 +#define BM_SAIF_STAT_RSRVD0 0x0000000E +#define BF_SAIF_STAT_RSRVD0(v) \ + (((v) << 1) & BM_SAIF_STAT_RSRVD0) +#define BM_SAIF_STAT_BUSY 0x00000001 + +/* SAFI_DATA */ +#define BP_SAIF_DATA_PCM_RIGHT 16 +#define BM_SAIF_DATA_PCM_RIGHT 0xFFFF0000 +#define BF_SAIF_DATA_PCM_RIGHT(v) \ + (((v) << 16) & BM_SAIF_DATA_PCM_RIGHT) +#define BP_SAIF_DATA_PCM_LEFT 0 +#define BM_SAIF_DATA_PCM_LEFT 0x0000FFFF +#define BF_SAIF_DATA_PCM_LEFT(v) \ + (((v) << 0) & BM_SAIF_DATA_PCM_LEFT) + +/* SAIF_VERSION */ +#define BP_SAIF_VERSION_MAJOR 24 +#define BM_SAIF_VERSION_MAJOR 0xFF000000 +#define BF_SAIF_VERSION_MAJOR(v) \ + (((v) << 24) & BM_SAIF_VERSION_MAJOR) +#define BP_SAIF_VERSION_MINOR 16 +#define BM_SAIF_VERSION_MINOR 0x00FF0000 +#define BF_SAIF_VERSION_MINOR(v) \ + (((v) << 16) & BM_SAIF_VERSION_MINOR) +#define BP_SAIF_VERSION_STEP 0 +#define BM_SAIF_VERSION_STEP 0x0000FFFF +#define BF_SAIF_VERSION_STEP(v) \ + (((v) << 0) & BM_SAIF_VERSION_STEP) + +#define MXS_SAIF_MCLK 0 + +#include "mxs-pcm.h" + +struct mxs_saif { + struct device *dev; + struct clk *clk; + unsigned int mclk; + unsigned int mclk_in_use; + void __iomem *base; + int irq; + struct mxs_pcm_dma_params dma_param; + unsigned int id; + unsigned int master_id; + unsigned int cur_rate; + unsigned int ongoing; + + u32 fifo_underrun; + u32 fifo_overrun; +}; + +extern int mxs_saif_put_mclk(unsigned int saif_id); +extern int mxs_saif_get_mclk(unsigned int saif_id, unsigned int mclk, + unsigned int rate); +#endif diff --git a/sound/soc/mxs/mxs-sgtl5000.c b/sound/soc/mxs/mxs-sgtl5000.c new file mode 100644 index 000000000..b1d9b5ebe --- /dev/null +++ b/sound/soc/mxs/mxs-sgtl5000.c @@ -0,0 +1,211 @@ +/* + * Copyright 2011 Freescale Semiconductor, Inc. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/soc-dapm.h> +#include <asm/mach-types.h> + +#include "../codecs/sgtl5000.h" +#include "mxs-saif.h" + +static int mxs_sgtl5000_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_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int rate = params_rate(params); + u32 dai_format, mclk; + int ret; + + /* sgtl5000 does not support 512*rate when in 96000 fs */ + switch (rate) { + case 96000: + mclk = 256 * rate; + break; + default: + mclk = 512 * rate; + break; + } + + /* Sgtl5000 sysclk should be >= 8MHz and <= 27M */ + if (mclk < 8000000 || mclk > 27000000) + return -EINVAL; + + /* Set SGTL5000's SYSCLK (provided by SAIF MCLK) */ + ret = snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, mclk, 0); + if (ret) + return ret; + + /* The SAIF MCLK should be the same as SGTL5000_SYSCLK */ + ret = snd_soc_dai_set_sysclk(cpu_dai, MXS_SAIF_MCLK, mclk, 0); + if (ret) + return ret; + + /* set codec to slave mode */ + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, dai_format); + if (ret) + return ret; + + return 0; +} + +static struct snd_soc_ops mxs_sgtl5000_hifi_ops = { + .hw_params = mxs_sgtl5000_hw_params, +}; + +static struct snd_soc_dai_link mxs_sgtl5000_dai[] = { + { + .name = "HiFi Tx", + .stream_name = "HiFi Playback", + .codec_dai_name = "sgtl5000", + .codec_name = "sgtl5000.0-000a", + .cpu_dai_name = "mxs-saif.0", + .platform_name = "mxs-saif.0", + .ops = &mxs_sgtl5000_hifi_ops, + }, { + .name = "HiFi Rx", + .stream_name = "HiFi Capture", + .codec_dai_name = "sgtl5000", + .codec_name = "sgtl5000.0-000a", + .cpu_dai_name = "mxs-saif.1", + .platform_name = "mxs-saif.1", + .ops = &mxs_sgtl5000_hifi_ops, + }, +}; + +static struct snd_soc_card mxs_sgtl5000 = { + .name = "mxs_sgtl5000", + .owner = THIS_MODULE, + .dai_link = mxs_sgtl5000_dai, + .num_links = ARRAY_SIZE(mxs_sgtl5000_dai), +}; + +static int mxs_sgtl5000_probe_dt(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *saif_np[2], *codec_np; + int i, ret = 0; + + if (!np) + return 1; /* no device tree */ + + saif_np[0] = of_parse_phandle(np, "saif-controllers", 0); + saif_np[1] = of_parse_phandle(np, "saif-controllers", 1); + codec_np = of_parse_phandle(np, "audio-codec", 0); + if (!saif_np[0] || !saif_np[1] || !codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + return -EINVAL; + } + + for (i = 0; i < 2; i++) { + mxs_sgtl5000_dai[i].codec_name = NULL; + mxs_sgtl5000_dai[i].codec_of_node = codec_np; + mxs_sgtl5000_dai[i].cpu_dai_name = NULL; + mxs_sgtl5000_dai[i].cpu_of_node = saif_np[i]; + mxs_sgtl5000_dai[i].platform_name = NULL; + mxs_sgtl5000_dai[i].platform_of_node = saif_np[i]; + } + + of_node_put(codec_np); + of_node_put(saif_np[0]); + of_node_put(saif_np[1]); + + return ret; +} + +static int mxs_sgtl5000_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mxs_sgtl5000; + int ret; + + ret = mxs_sgtl5000_probe_dt(pdev); + if (ret < 0) + return ret; + + /* + * Set an init clock(11.28Mhz) for sgtl5000 initialization(i2c r/w). + * The Sgtl5000 sysclk is derived from saif0 mclk and it's range + * should be >= 8MHz and <= 27M. + */ + ret = mxs_saif_get_mclk(0, 44100 * 256, 44100); + if (ret) + return ret; + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + return ret; + } + + return 0; +} + +static int mxs_sgtl5000_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + mxs_saif_put_mclk(0); + + snd_soc_unregister_card(card); + + return 0; +} + +static const struct of_device_id mxs_sgtl5000_dt_ids[] = { + { .compatible = "fsl,mxs-audio-sgtl5000", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_sgtl5000_dt_ids); + +static struct platform_driver mxs_sgtl5000_audio_driver = { + .driver = { + .name = "mxs-sgtl5000", + .owner = THIS_MODULE, + .of_match_table = mxs_sgtl5000_dt_ids, + }, + .probe = mxs_sgtl5000_probe, + .remove = mxs_sgtl5000_remove, +}; + +module_platform_driver(mxs_sgtl5000_audio_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXS ALSA SoC Machine driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mxs-sgtl5000"); |
