diff options
author | Alison Wang <b18965@freescale.com> | 2012-07-27 11:10:54 +0800 |
---|---|---|
committer | Justin Waters <justin.waters@timesys.com> | 2012-09-12 16:49:52 -0400 |
commit | 74a8bbab14c2f5e9e1fd3a263b090484a988f673 (patch) | |
tree | d9efba7d939e75c2c43bd5654c3291029c1e4a89 /sound | |
parent | cb9324a27200e6ba9c39a0e2536336d8cb35d3ab (diff) |
ENGR00212251-2: sai: add SAI driver support for Faraday
Add SAI driver support for Faraday.
Signed-off-by: Alison Wang <b18965@freescale.com>
Signed-off-by: Xiaochun Li <b41219@freescale.com>
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/Kconfig | 1 | ||||
-rw-r--r-- | sound/soc/Makefile | 1 | ||||
-rw-r--r-- | sound/soc/codecs/sgtl5000.c | 16 | ||||
-rw-r--r-- | sound/soc/mvf/Kconfig | 26 | ||||
-rw-r--r-- | sound/soc/mvf/Makefile | 11 | ||||
-rw-r--r-- | sound/soc/mvf/mvf-pcm-dma-twr.c | 403 | ||||
-rw-r--r-- | sound/soc/mvf/mvf-sai.c | 642 | ||||
-rw-r--r-- | sound/soc/mvf/mvf-sai.h | 160 | ||||
-rw-r--r-- | sound/soc/mvf/mvf-sgtl5000.c | 320 |
9 files changed, 1575 insertions, 5 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 8224db5f0434..1dacae842f74 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -46,6 +46,7 @@ source "sound/soc/davinci/Kconfig" source "sound/soc/ep93xx/Kconfig" source "sound/soc/fsl/Kconfig" source "sound/soc/imx/Kconfig" +source "sound/soc/mvf/Kconfig" source "sound/soc/jz4740/Kconfig" source "sound/soc/nuc900/Kconfig" source "sound/soc/omap/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 1ed61c5df2c5..96471b431b3b 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_SND_SOC) += davinci/ obj-$(CONFIG_SND_SOC) += ep93xx/ obj-$(CONFIG_SND_SOC) += fsl/ obj-$(CONFIG_SND_SOC) += imx/ +obj-$(CONFIG_SND_SOC) += mvf/ obj-$(CONFIG_SND_SOC) += jz4740/ obj-$(CONFIG_SND_SOC) += mid-x86/ obj-$(CONFIG_SND_SOC) += nuc900/ diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c index fd055146192d..687b62895bb2 100644 --- a/sound/soc/codecs/sgtl5000.c +++ b/sound/soc/codecs/sgtl5000.c @@ -1,7 +1,7 @@ /* * sgtl5000.c -- SGTL5000 ALSA SoC Audio driver * - * Copyright 2010-2011 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2010-2012 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 version 2 as @@ -811,6 +811,7 @@ static int sgtl5000_set_clock(struct snd_soc_codec *codec, int frame_rate) * factor of freq =96k can only be 256, since mclk in range (12m,27m) */ switch (sgtl5000->sysclk / sys_fs) { +#ifndef CONFIG_IMX_HAVE_PLATFORM_MVF_SAI case 256: clk_ctl |= SGTL5000_MCLK_FREQ_256FS << SGTL5000_MCLK_FREQ_SHIFT; @@ -823,6 +824,7 @@ static int sgtl5000_set_clock(struct snd_soc_codec *codec, int frame_rate) clk_ctl |= SGTL5000_MCLK_FREQ_512FS << SGTL5000_MCLK_FREQ_SHIFT; break; +#endif default: /* if mclk not satisify the divider, use pll */ if (sgtl5000->master) { @@ -1103,7 +1105,11 @@ static int ldo_regulator_register(struct snd_soc_codec *codec, struct regulator_init_data *init_data, int voltage) { +#ifdef CONFIG_IMX_HAVE_PLATFORM_MVF_SAI + return 0; +#else return -EINVAL; +#endif } static int ldo_regulator_remove(struct snd_soc_codec *codec) @@ -1193,7 +1199,7 @@ static struct snd_soc_dai_driver sgtl5000_dai = { .name = "sgtl5000", .playback = { .stream_name = "Playback", - .channels_min = 2, + .channels_min = 1, .channels_max = 2, /* * only support 8~48K + 96K, @@ -1204,7 +1210,7 @@ static struct snd_soc_dai_driver sgtl5000_dai = { }, .capture = { .stream_name = "Capture", - .channels_min = 2, + .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000, .formats = SGTL5000_FORMATS, @@ -1505,7 +1511,6 @@ static int sgtl5000_enable_regulators(struct snd_soc_codec *codec) /* free VDDD regulator */ regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies), sgtl5000->supplies); - ret = ldo_regulator_register(codec, &ldo_init_data, voltage); if (ret) return ret; @@ -1580,11 +1585,12 @@ static int sgtl5000_probe(struct snd_soc_codec *codec) sgtl5000_fill_reg_cache(codec); +#ifndef CONFIG_IMX_HAVE_PLATFORM_MVF_SAI /* power up sgtl5000 */ ret = sgtl5000_set_power_regs(codec); if (ret) goto err; - +#endif /* enable small pop, introduce 400ms delay in turning off */ snd_soc_update_bits(codec, SGTL5000_CHIP_REF_CTRL, SGTL5000_SMALL_POP, diff --git a/sound/soc/mvf/Kconfig b/sound/soc/mvf/Kconfig new file mode 100644 index 000000000000..6577df843926 --- /dev/null +++ b/sound/soc/mvf/Kconfig @@ -0,0 +1,26 @@ +menuconfig SND_MVF_SOC + tristate "SoC Audio for Freescale Faraday CPUs" + depends on ARCH_MVF && IMX_HAVE_PLATFORM_MVF_SAI + select SND_PCM + default y + help + Say Y or M if you want to add support for codecs attached to + the Faraday SAI interface. + + +if SND_MVF_SOC + +config SND_MVF_SOC_TWR + tristate + +config SND_SOC_MVF_SGTL5000 + tristate "SoC Audio support for Faraday boards with sgtl5000" + depends on I2C + select SND_SOC_SGTL5000 + select SND_MVF_SOC_TWR + default y + help + Say Y if you want to add support for SoC audio on an Farday board with + a sgtl5000 codec. + +endif diff --git a/sound/soc/mvf/Makefile b/sound/soc/mvf/Makefile new file mode 100644 index 000000000000..c9e179ad0a8e --- /dev/null +++ b/sound/soc/mvf/Makefile @@ -0,0 +1,11 @@ +# Faraday Platform Support +snd-soc-mvf-objs := mvf-sai.o +snd-soc-mvf-twr-objs := mvf-pcm-dma-twr.o + +obj-$(CONFIG_SND_MVF_SOC) += snd-soc-mvf.o +obj-$(CONFIG_SND_MVF_SOC_TWR) += snd-soc-mvf-twr.o + +# Faraday Machine Support +snd-soc-mvf-sgtl5000-objs := mvf-sgtl5000.o + +obj-$(CONFIG_SND_SOC_MVF_SGTL5000) += snd-soc-mvf-sgtl5000.o diff --git a/sound/soc/mvf/mvf-pcm-dma-twr.c b/sound/soc/mvf/mvf-pcm-dma-twr.c new file mode 100644 index 000000000000..83832735408c --- /dev/null +++ b/sound/soc/mvf/mvf-pcm-dma-twr.c @@ -0,0 +1,403 @@ +/* + * mvf-pcm-dma-twr.c -- ALSA Soc Audio Layer + * + * Copyright 2012 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. + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dmaengine.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <mach/dma.h> +#include <mach/mvf_edma.h> +#include <mach/mcf_edma.h> + +#include "mvf-sai.h" + + +#define DRIVER_NAME "mvf-pcm-audio" +#define TCD_NUMBER 4 +#define EDMA_PRIO_HIGH 6 + +struct edma_tcd { + __le32 saddr; /* source address */ + __le16 soffset; /* source offset */ + __le16 attr; /* transfer attribute */ + __le32 nbytes; /* minor byte count */ + __le32 slast; /* last source address adjust */ + __le32 daddr; /* dest address */ + __le16 doffset; /* dest offset */ + __le16 citer; /* current minor looplink, major count */ + __le32 dlast_sga; /* last dest addr adjust, scatter/gather addr*/ + __le16 csr; /* control and status */ + __le16 biter; /* begging minor looklink, major count */ +}; + +struct mvf_pcm_runtime_data { + struct edma_tcd tcd[TCD_NUMBER]; + /* physical address of mvf_pcm_runtime_data */ + dma_addr_t tcd_buf_phys; + dma_addr_t dma_buf_phys; + dma_addr_t dma_buf_next; + dma_addr_t dma_buf_end; + + int dma_chan; /* channel number */ + int tcd_chan; + dma_addr_t src_addr; + dma_addr_t dst_addr; + __le16 soffset; + __le16 doffset; + struct imx_dma_data dma_data; + + int period_bytes; + int periods; + int dma; + unsigned long offset; + unsigned long size; + int period_time; +}; + +static irqreturn_t audio_dma_irq(int channel, void *data) +{ + struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct mvf_pcm_runtime_data *iprtd = runtime->private_data; + + iprtd->offset += iprtd->period_bytes; + iprtd->offset %= iprtd->period_bytes * iprtd->periods; + + snd_pcm_period_elapsed(substream); + + mcf_edma_confirm_interrupt_handled(iprtd->dma_chan); + + return IRQ_HANDLED; +} + +static int edma_request_channel(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mvf_pcm_runtime_data *iprtd = runtime->private_data; + int err; + + err = mcf_edma_request_channel(iprtd->dma_chan, audio_dma_irq, NULL, + iprtd->dma_data.priority, substream, NULL, DRIVER_NAME); + + return err; +} + +static int mvf_sai_dma_alloc(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_pcm_dma_params *dma_params; + struct snd_pcm_runtime *runtime = substream->runtime; + struct mvf_pcm_runtime_data *iprtd = runtime->private_data; + + dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + iprtd->dma_data.priority = EDMA_PRIO_HIGH; + iprtd->dma_data.dma_request = dma_params->dma; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + iprtd->dma_chan = DMA_MUX_SAI2_TX; + else + iprtd->dma_chan = DMA_MUX_SAI2_RX; + + iprtd->tcd_chan = edma_request_channel(substream); + if (iprtd->tcd_chan < 0) + return -EINVAL; + + return 0; +} + +void fill_tcd_params(void *base, u32 source, u32 dest, u32 attr, u32 soff, + u32 nbytes, u32 slast, u32 citer, u32 biter, u32 doff, u32 dlast_sga, + int major_int, int disable_req, int enable_sg) +{ + struct edma_tcd *tcd = (struct edma_tcd *)base; + + tcd->saddr = source; + tcd->attr = attr; + tcd->soffset = soff; + tcd->nbytes = nbytes; + tcd->slast = slast; + tcd->daddr = dest; + tcd->citer = citer & 0x7fff; + tcd->doffset = doff; + tcd->dlast_sga = dlast_sga; + tcd->biter = biter & 0x7fff; + tcd->csr = ((major_int) ? 0x2 : 0) | ((disable_req) ? 0x8 : 0) | + ((enable_sg) ? 0x10 : 0); +} + +static int edma_engine_config(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct mvf_pcm_runtime_data *iprtd = runtime->private_data; + u32 size = frames_to_bytes(runtime, runtime->period_size); + struct imx_pcm_dma_params *dma_params; + u32 sg_addr; + int i; + + dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + iprtd->dma_buf_phys = runtime->dma_addr; + iprtd->dma_buf_next = iprtd->dma_buf_phys; + iprtd->dma_buf_end = iprtd->dma_buf_phys + runtime->periods * size; + + sg_addr = iprtd->tcd_buf_phys; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + iprtd->src_addr = iprtd->dma_buf_next; + iprtd->dst_addr = dma_params->dma_addr; + iprtd->soffset = 2; + iprtd->doffset = 0; + } else { + iprtd->src_addr = dma_params->dma_addr; + iprtd->dst_addr = iprtd->dma_buf_next; + iprtd->soffset = 0; + iprtd->doffset = 2; + } + + mcf_edma_set_tcd_params(iprtd->tcd_chan, + iprtd->src_addr, iprtd->dst_addr, + MCF_EDMA_TCD_ATTR_SSIZE_16BIT | MCF_EDMA_TCD_ATTR_DSIZE_16BIT, + iprtd->soffset, 4, 0, size / 4, size / 4, iprtd->doffset, + sg_addr, 1, 0, 1); + + for (i = 0; i < TCD_NUMBER; i++) { + iprtd->dma_buf_next += size; + if (iprtd->dma_buf_next >= iprtd->dma_buf_end) + iprtd->dma_buf_next = iprtd->dma_buf_phys; + + sg_addr = iprtd->tcd_buf_phys + + ((i + 1) % TCD_NUMBER) * sizeof(struct edma_tcd); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + iprtd->src_addr = iprtd->dma_buf_next; + else + iprtd->dst_addr = iprtd->dma_buf_next; + + fill_tcd_params(&iprtd->tcd[i], + iprtd->src_addr, iprtd->dst_addr, + MCF_EDMA_TCD_ATTR_SSIZE_16BIT | + MCF_EDMA_TCD_ATTR_DSIZE_16BIT, + iprtd->soffset, 4, 0, size / 4, size / 4, + iprtd->doffset, sg_addr, 1, 0, 1); + } + + return 0; +} + +static int snd_mvf_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_pcm_runtime *runtime = substream->runtime; + struct mvf_pcm_runtime_data *iprtd = runtime->private_data; + struct imx_pcm_dma_params *dma_params; + int ret; + + dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + ret = mvf_sai_dma_alloc(substream, params); + if (ret) + return ret; + + iprtd->size = params_buffer_bytes(params); + iprtd->periods = params_periods(params); + iprtd->period_bytes = params_period_bytes(params); + iprtd->offset = 0; + iprtd->period_time = HZ / (params_rate(params) / + params_period_size(params)); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static int snd_mvf_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mvf_pcm_runtime_data *iprtd = runtime->private_data; + + if (iprtd->dma_chan) + mcf_edma_free_channel(iprtd->tcd_chan, substream); + + return 0; +} + +static int snd_mvf_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mvf_pcm_dma_params *dma_params; + int ret; + + dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + ret = edma_engine_config(substream); + if (ret) + return ret; + + return 0; +} + +static int snd_mvf_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mvf_pcm_runtime_data *iprtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + mcf_edma_start_transfer(iprtd->tcd_chan); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + mcf_edma_stop_transfer(iprtd->tcd_chan); + break; + default: + return -EINVAL; + } + + return 0; +} + +static +snd_pcm_uframes_t snd_mvf_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mvf_pcm_runtime_data *iprtd = runtime->private_data; + + return bytes_to_frames(substream->runtime, iprtd->offset); +} + +static struct snd_pcm_hardware snd_mvf_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_min = 8000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = MVF_SAI_DMABUF_SIZE, + .period_bytes_min = 4096, + .period_bytes_max = MVF_SAI_DMABUF_SIZE / TCD_NUMBER, + .periods_min = TCD_NUMBER, + .periods_max = TCD_NUMBER, + .fifo_size = 0, +}; + +static int snd_mvf_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mvf_pcm_runtime_data *iprtd; + dma_addr_t tcd_buf_phys; + int ret; + + iprtd = dma_alloc_coherent(substream->pcm->dev, sizeof(*iprtd), + &tcd_buf_phys, GFP_KERNEL); + if (iprtd == NULL) + return -ENOMEM; + + iprtd->tcd_buf_phys = tcd_buf_phys; + runtime->private_data = iprtd; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + kfree(iprtd); + return ret; + } + + snd_soc_set_runtime_hwparams(substream, &snd_mvf_hardware); + + return 0; +} + +static int snd_mvf_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct mvf_pcm_runtime_data *iprtd = runtime->private_data; + + dma_free_coherent(substream->pcm->dev, sizeof(*iprtd), iprtd, + iprtd->tcd_buf_phys); + + return 0; +} + +static struct snd_pcm_ops mvf_pcm_ops = { + .open = snd_mvf_open, + .close = snd_mvf_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_mvf_pcm_hw_params, + .hw_free = snd_mvf_pcm_hw_free, + .prepare = snd_mvf_pcm_prepare, + .trigger = snd_mvf_pcm_trigger, + .pointer = snd_mvf_pcm_pointer, + .mmap = snd_mvf_pcm_mmap, +}; + +static struct snd_soc_platform_driver mvf_soc_platform = { + .ops = &mvf_pcm_ops, + .pcm_new = mvf_pcm_new, + .pcm_free = mvf_pcm_free, +}; + +static int __devinit mvf_soc_platform_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, &mvf_soc_platform); +} + +static int __devexit mvf_soc_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver mvf_pcm_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = mvf_soc_platform_probe, + .remove = __devexit_p(mvf_soc_platform_remove), +}; + +static int __init snd_mvf_pcm_init(void) +{ + return platform_driver_register(&mvf_pcm_driver); +} +module_init(snd_mvf_pcm_init); + +static void __exit snd_mvf_pcm_exit(void) +{ + platform_driver_unregister(&mvf_pcm_driver); +} +module_exit(snd_mvf_pcm_exit); + +MODULE_AUTHOR("Alison Wang, <b18965@freescale.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mvf-pcm-audio"); diff --git a/sound/soc/mvf/mvf-sai.c b/sound/soc/mvf/mvf-sai.c new file mode 100644 index 000000000000..68b819d78aee --- /dev/null +++ b/sound/soc/mvf/mvf-sai.c @@ -0,0 +1,642 @@ +/* + * mvf-sai.c -- ALSA Soc Audio Layer + * + * Copyright 2012 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. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <mach/sai.h> +#include <mach/hardware.h> + +#include "mvf-sai.h" + +#define MVF_SAI_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +/* SAI Network Mode or TDM slots configuration */ +static int mvf_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct mvf_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + u32 tcr4, rcr4; + + if (sai->play_enabled == 0) { + tcr4 = readl(sai->base + SAI_TCR4); + tcr4 &= ~SAI_TCR4_FRSZ_MASK; + tcr4 |= SAI_TCR4_FRSZ(1); + writel(tcr4, sai->base + SAI_TCR4); + writel(tx_mask, sai->base + SAI_TMR); + } + + if (sai->cap_enabled == 0) { + rcr4 = readl(sai->base + SAI_RCR4); + rcr4 &= ~SAI_RCR4_FRSZ_MASK; + rcr4 |= SAI_RCR4_FRSZ(1); + writel(rcr4, sai->base + SAI_RCR4); + writel(rx_mask, sai->base + SAI_RMR); + } + return 0; +} + +/* SAI DAI format configuration */ +static int mvf_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct mvf_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + u32 tcr2, tcr3, tcr4; + u32 rcr2 = 0; + + tcr2 = readl(sai->base + SAI_TCR2); + tcr3 = readl(sai->base + SAI_TCR3); + tcr4 = readl(sai->base + SAI_TCR4); + + tcr4 |= SAI_TCR4_MF; + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* data on rising edge of bclk, frame low 1clk before data */ + tcr4 |= SAI_TCR4_FSE; + tcr4 |= SAI_TCR4_FSP; + break; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + tcr4 |= SAI_TCR4_FSP; + tcr2 &= ~SAI_TCR2_BCP; + break; + case SND_SOC_DAIFMT_IB_NF: + tcr4 &= ~SAI_TCR4_FSP; + tcr2 &= ~SAI_TCR2_BCP; + break; + case SND_SOC_DAIFMT_NB_IF: + tcr4 |= SAI_TCR4_FSP; + tcr2 |= SAI_TCR2_BCP; + break; + case SND_SOC_DAIFMT_NB_NF: + tcr4 &= ~SAI_TCR4_FSP; + tcr2 |= SAI_TCR2_BCP; + break; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + tcr2 |= SAI_TCR2_BCD_MSTR; + tcr4 |= SAI_TCR4_FSD_MSTR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + tcr2 &= ~SAI_TCR2_BCD_MSTR; + tcr4 &= ~SAI_TCR4_FSD_MSTR; + break; + default: + return -EINVAL; + } + + tcr3 |= SAI_TCR3_TCE; + + if (sai->flags & MVF_SAI_TRA_SYN) { + rcr2 = tcr2; + rcr2 |= SAI_TCR2_SYNC; + } + + if (sai->play_enabled == 0) { + writel(tcr2, sai->base + SAI_TCR2); + writel(tcr3, sai->base + SAI_TCR3); + writel(tcr4, sai->base + SAI_TCR4); + } + + if (sai->cap_enabled == 0) { + writel(rcr2, sai->base + SAI_RCR2); + writel(tcr3, sai->base + SAI_RCR3); + writel(tcr4, sai->base + SAI_RCR4); + } + return 0; +} + +/* SAI system clock configuration */ +static int mvf_sai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct mvf_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + u32 tcr2; + + tcr2 = readl(sai->base + SAI_TCR2); + + if (dir == SND_SOC_CLOCK_IN) + return 0; + + switch (clk_id) { + case MVF_SAI_BUS_CLK: + tcr2 &= ~SAI_TCR2_MSEL_MASK; + tcr2 |= SAI_TCR2_MSEL_BUS; + break; + case MVF_SAI_MAST_CLK1: + tcr2 &= ~SAI_TCR2_MSEL_MASK; + tcr2 |= SAI_TCR2_MSEL_MCLK1; + break; + case MVF_SAI_MAST_CLK2: + tcr2 &= ~SAI_TCR2_MSEL_MASK; + tcr2 |= SAI_TCR2_MSEL_MCLK2; + break; + case MVF_SAI_MAST_CLK3: + tcr2 &= ~SAI_TCR2_MSEL_MASK; + tcr2 |= SAI_TCR2_MSEL_MCLK3; + break; + default: + return -EINVAL; + } + + if (sai->play_enabled == 0) + writel(tcr2, sai->base + SAI_TCR2); + if (sai->cap_enabled == 0) + writel(tcr2, sai->base + SAI_RCR2); + return 0; +} + +/* SAI Clock dividers */ +static int mvf_sai_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + struct mvf_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + u32 tcr2, rcr2; + + tcr2 = readl(sai->base + SAI_TCR2); + rcr2 = readl(sai->base + SAI_RCR2); + + switch (div_id) { + case MVF_SAI_TX_DIV: + tcr2 &= ~SAI_TCR2_DIV_MASK; + tcr2 |= SAI_TCR2_DIV(div); + break; + case MVF_SAI_RX_DIV: + rcr2 &= ~SAI_RCR2_DIV_MASK; + rcr2 |= SAI_RCR2_DIV(div); + break; + default: + return -EINVAL; + } + + if (sai->play_enabled == 0) + writel(tcr2, sai->base + SAI_TCR2); + if (sai->cap_enabled == 0) + writel(rcr2, sai->base + SAI_RCR2); + return 0; +} + +static int mvf_sai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct mvf_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + struct imx_pcm_dma_params *dma_data; + u32 tcr4, tcr5; + + /* Tx/Rx config */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = &sai->dma_params_tx; + else + dma_data = &sai->dma_params_rx; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); + + tcr4 = readl(sai->base + SAI_TCR4); + tcr4 &= ~SAI_TCR4_SYWD_MASK; + + tcr5 = readl(sai->base + SAI_TCR5); + tcr5 &= ~SAI_TCR5_WNW_MASK; + tcr5 &= ~SAI_TCR5_W0W_MASK; + tcr5 &= ~SAI_TCR5_FBT_MASK; + + /* DAI data (word) size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + tcr4 |= SAI_TCR4_SYWD(16 - 1); + tcr5 |= SAI_TCR5_WNW(16 - 1); + tcr5 |= SAI_TCR5_W0W(16 - 1); + tcr5 |= SAI_TCR5_FBT(16 - 1); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + tcr4 |= SAI_TCR4_SYWD(20 - 1); + tcr5 |= SAI_TCR5_WNW(20 - 1); + tcr5 |= SAI_TCR5_W0W(20 - 1); + tcr5 |= SAI_TCR5_FBT(20 - 1); + break; + case SNDRV_PCM_FORMAT_S24_LE: + tcr4 |= SAI_TCR4_SYWD(24 - 1); + tcr5 |= SAI_TCR5_WNW(24 - 1); + tcr5 |= SAI_TCR5_W0W(24 - 1); + tcr5 |= SAI_TCR5_FBT(24 - 1); + break; + } + + writel(tcr4, sai->base + SAI_TCR4); + writel(tcr5, sai->base + SAI_TCR5); + writel(tcr4, sai->base + SAI_RCR4); + writel(tcr5, sai->base + SAI_RCR5); + return 0; +} + +static int mvf_sai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct mvf_sai *sai = snd_soc_dai_get_drvdata(dai); + unsigned int tcsr, rcsr; + + tcsr = readl(sai->base + SAI_TCSR); + rcsr = readl(sai->base + SAI_RCSR); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sai->play_enabled = 1; + if (sai->flags & MVF_SAI_DMA) + tcsr |= SAI_TCSR_FRDE; + else + tcsr |= SAI_TCSR_FRIE | SAI_TCSR_FWF; + } else { + sai->cap_enabled = 1; + if (sai->flags & MVF_SAI_DMA) + rcsr |= SAI_RCSR_FRDE; + else + rcsr |= SAI_RCSR_FRIE | SAI_RCSR_FWF; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + rcsr |= SAI_RCSR_RE; + tcsr |= SAI_TCSR_TE; + writel(tcsr, sai->base + SAI_TCSR); + writel(rcsr, sai->base + SAI_RCSR); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + tcsr &= ~SAI_TCSR_TE; + rcsr &= ~SAI_RCSR_RE; + if (!(dai->playback_active & dai->capture_active)) { + writel(tcsr, sai->base + SAI_TCSR); + writel(rcsr, sai->base + SAI_RCSR); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int mvf_sai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct mvf_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + + if (cpu_dai->playback_active || cpu_dai->capture_active) + return 0; + + clk_enable(sai->clk); + + return 0; +} + +static void mvf_sai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct mvf_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + + if (cpu_dai->playback_active == 0) + sai->play_enabled = 0; + if (cpu_dai->capture_active == 0) + sai->cap_enabled = 0; + + /* shutdown SAI if neither Tx or Rx is active */ + if (cpu_dai->playback_active || cpu_dai->capture_active) + return; + + clk_disable(sai->clk); +} + +static struct snd_soc_dai_ops mvf_sai_pcm_dai_ops = { + .hw_params = mvf_sai_hw_params, + .set_fmt = mvf_sai_set_dai_fmt, + .set_clkdiv = mvf_sai_set_dai_clkdiv, + .set_sysclk = mvf_sai_set_dai_sysclk, + .set_tdm_slot = mvf_sai_set_dai_tdm_slot, + .trigger = mvf_sai_trigger, + .startup = mvf_sai_startup, + .shutdown = mvf_sai_shutdown, +}; + +int snd_mvf_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + ret = dma_mmap_coherent(NULL, vma, runtime->dma_area, + runtime->dma_addr, runtime->dma_bytes); + + pr_debug("%s: ret: %d %p 0x%08x 0x%08x\n", __func__, ret, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); + return ret; +} +EXPORT_SYMBOL_GPL(snd_mvf_pcm_mmap); + +static int mvf_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = MVF_SAI_DMABUF_SIZE; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + + return 0; +} + +static u64 mvf_pcm_dmamask = DMA_BIT_MASK(32); + +int mvf_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &mvf_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + if (dai->driver->playback.channels_min) { + ret = mvf_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->driver->capture.channels_min) { + ret = mvf_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + +out: + return ret; +} +EXPORT_SYMBOL_GPL(mvf_pcm_new); + +void mvf_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_writecombine(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} +EXPORT_SYMBOL_GPL(mvf_pcm_free); + +static int mvf_sai_dai_probe(struct snd_soc_dai *dai) +{ + struct mvf_sai *sai = dev_get_drvdata(dai->dev); + + snd_soc_dai_set_drvdata(dai, sai); + + writel(sai->dma_params_tx.burstsize, sai->base + SAI_TCR1); + writel(sai->dma_params_rx.burstsize, sai->base + SAI_RCR1); + + return 0; +} + +#ifdef CONFIG_PM +static int mvf_sai_dai_suspend(struct snd_soc_dai *dai) +{ + return 0; +} + +static int mvf_sai_dai_resume(struct snd_soc_dai *dai) +{ + return 0; +} +#else +#define mvf_sai_dai_suspend NULL +#define mvf_sai_dai_resume NULL +#endif + +static struct snd_soc_dai_driver mvf_sai_dai = { + .probe = mvf_sai_dai_probe, + .suspend = mvf_sai_dai_suspend, + .resume = mvf_sai_dai_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = MVF_SAI_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = MVF_SAI_FORMATS, + }, + .ops = &mvf_sai_pcm_dai_ops, +}; + +static int mvf_sai_probe(struct platform_device *pdev) +{ + struct resource *res; + struct mvf_sai *sai; + struct mvf_sai_platform_data *pdata = pdev->dev.platform_data; + int ret = 0; + struct snd_soc_dai_driver *dai; + + sai = kzalloc(sizeof(*sai), GFP_KERNEL); + if (!sai) + return -ENOMEM; + dev_set_drvdata(&pdev->dev, sai); + + if (pdata) + sai->flags = pdata->flags; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + ret = -ENODEV; + goto failed_clk; + } + sai->irq = res->start; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + goto failed_get_resource; + } + + if (!request_mem_region(res->start, resource_size(res), DRV_NAME)) { + dev_err(&pdev->dev, "request_mem_region failed\n"); + ret = -EBUSY; + goto failed_get_resource; + } + + sai->base = ioremap(res->start, resource_size(res)); + if (!sai->base) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENODEV; + goto failed_ioremap; + } + + sai->clk = clk_get(&pdev->dev, "sai_clk"); + if (IS_ERR(sai->clk)) { + ret = PTR_ERR(sai->clk); + dev_err(&pdev->dev, "Cannot get the clock: %d\n", + ret); + goto failed_clk; + } + clk_enable(sai->clk); + + if (sai->flags & MVF_SAI_USE_I2S_SLAVE) + dai = &mvf_sai_dai; + + writel(0x0, sai->base + SAI_TCSR); + writel(0x0, sai->base + SAI_RCSR); + + sai->dma_params_rx.dma_addr = res->start + SAI_RDR; + sai->dma_params_tx.dma_addr = res->start + SAI_TDR; + + sai->dma_params_tx.burstsize = 6; + sai->dma_params_rx.burstsize = 6; + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0"); + if (res) + sai->dma_params_tx.dma = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0"); + if (res) + sai->dma_params_rx.dma = res->start; + + platform_set_drvdata(pdev, sai); + + ret = snd_soc_register_dai(&pdev->dev, dai); + if (ret) { + dev_err(&pdev->dev, "register DAI failed\n"); + goto failed_register; + } + + sai->soc_platform_pdev = + platform_device_alloc("mvf-pcm-audio", pdev->id); + if (!sai->soc_platform_pdev) { + ret = -ENOMEM; + goto failed_pdev_alloc; + } + + platform_set_drvdata(sai->soc_platform_pdev, sai); + ret = platform_device_add(sai->soc_platform_pdev); + if (ret) { + dev_err(&pdev->dev, "failed to add platform device\n"); + goto failed_pdev_add; + } + + return 0; + +failed_pdev_add: + platform_device_put(sai->soc_platform_pdev); +failed_pdev_alloc: + snd_soc_unregister_dai(&pdev->dev); +failed_register: + iounmap(sai->base); +failed_ioremap: + release_mem_region(res->start, resource_size(res)); +failed_get_resource: + clk_disable(sai->clk); + clk_put(sai->clk); +failed_clk: + kfree(sai); + + return ret; +} + +static int __devexit mvf_sai_remove(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct mvf_sai *sai = platform_get_drvdata(pdev); + + platform_device_unregister(sai->soc_platform_pdev); + + snd_soc_unregister_dai(&pdev->dev); + + iounmap(sai->base); + release_mem_region(res->start, resource_size(res)); + clk_disable(sai->clk); + kfree(sai); + + return 0; +} + +static struct platform_driver mvf_sai_driver = { + .probe = mvf_sai_probe, + .remove = __devexit_p(mvf_sai_remove), + + .driver = { + .name = "mvf-sai", + .owner = THIS_MODULE, + }, +}; + +static int __init mvf_sai_init(void) +{ + return platform_driver_register(&mvf_sai_driver); +} + +static void __exit mvf_sai_exit(void) +{ + platform_driver_unregister(&mvf_sai_driver); +} + +module_init(mvf_sai_init); +module_exit(mvf_sai_exit); + +/* Module information */ +MODULE_AUTHOR("Alison Wang, <b18965@freescale.com>"); +MODULE_DESCRIPTION("Faraday I2S SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mvf-sai"); diff --git a/sound/soc/mvf/mvf-sai.h b/sound/soc/mvf/mvf-sai.h new file mode 100644 index 000000000000..1406e28883a0 --- /dev/null +++ b/sound/soc/mvf/mvf-sai.h @@ -0,0 +1,160 @@ +/* + * Copyright 2012 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 version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _MVF_SAI_H +#define _MVF_SAI_H + +#define SAI_TX_DIV 0 + +#define SAI_TCSR 0x00 +#define SAI_TCSR_TE (1 << 31) +#define SAI_TCSR_FWF (1 << 17) +#define SAI_TCSR_FRIE (1 << 8) +#define SAI_TCSR_FRDE (1 << 0) + +#define SAI_TCR1 0x04 + +#define SAI_TCR2 0x08 +#define SAI_TCR2_SYNC (1 << 30) +#define SAI_TCR2_MSEL_MASK (0xff << 26) +#define SAI_TCR2_MSEL_BUS (0 << 26) +#define SAI_TCR2_MSEL_MCLK1 (1 << 26) +#define SAI_TCR2_MSEL_MCLK2 (2 << 26) +#define SAI_TCR2_MSEL_MCLK3 (3 << 26) +/* Bit clock is active low with driver outputs on + * falling edge and sample inputs on rising edge */ +#define SAI_TCR2_BCP (1 << 25) +#define SAI_TCR2_BCD_MSTR (1 << 24) +#define SAI_TCR2_DIV(x) (x) +#define SAI_TCR2_DIV_MASK 0xff + +#define SAI_TCR3 0x0c +#define SAI_TCR3_TCE (1 << 16) +#define SAI_TCR3_WDFL(x) (x) +#define SAI_TCR3_WDFL_MASK 0x1f + +#define SAI_TCR4 0x10 +#define SAI_TCR4_FRSZ(x) (x << 16) +#define SAI_TCR4_FRSZ_MASK (0x1f << 16) +#define SAI_TCR4_SYWD(x) ((x) << 8) +#define SAI_TCR4_SYWD_MASK (0x1f << 8) +#define SAI_TCR4_MF (1 << 4) +/* Frame sync is active low */ +#define SAI_TCR4_FSE (1 << 3) +#define SAI_TCR4_FSP (1 << 1) +#define SAI_TCR4_FSD_MSTR (1 << 0) + +#define SAI_TCR5 0x14 +#define SAI_TCR5_WNW(x) ((x) << 24) +#define SAI_TCR5_WNW_MASK (0x1f << 24) +#define SAI_TCR5_W0W(x) ((x) << 16) +#define SAI_TCR5_W0W_MASK (0x1f << 16) +#define SAI_TCR5_FBT(x) ((x) << 8) +#define SAI_TCR5_FBT_MASK (0x1f << 8) + +#define SAI_TDR 0x20 + +#define SAI_TFR 0x40 + +#define SAI_TMR 0x60 + + +#define SAI_RCSR 0x80 +#define SAI_RCSR_RE (1 << 31) +#define SAI_RCSR_FWF (1 << 17) +#define SAI_RCSR_FRIE (1 << 8) +#define SAI_RCSR_FRDE (1 << 0) + +#define SAI_RCR1 0x84 + +#define SAI_RCR2 0x88 +#define SAI_RCR2_MSEL_MASK (0xff << 26) +#define SAI_RCR2_MSEL_BUS (0 << 26) +#define SAI_RCR2_MSEL_MCLK1 (1 << 26) +#define SAI_RCR2_MSEL_MCLK2 (2 << 26) +#define SAI_RCR2_MSEL_MCLK3 (3 << 26) +/* Bit clock is active low with driver outputs on + * falling edge and sample inputs on rising edge */ +#define SAI_RCR2_BCP (1 << 25) +#define SAI_RCR2_BCD_MSTR (1 << 24) +#define SAI_RCR2_DIV(x) (x) +#define SAI_RCR2_DIV_MASK 0xff + +#define SAI_RCR3 0x8c +#define SAI_RCR3_TCE (1 << 16) +#define SAI_RCR3_WDFL(x) (x) +#define SAI_RCR3_WDFL_MASK 0x1f + +#define SAI_RCR4 0x90 +/* Frame sync is active low */ +#define SAI_RCR4_FRSZ(x) (x << 16) +#define SAI_RCR4_FRSZ_MASK (0x1f << 16) +#define SAI_RCR4_SYWD(x) (x << 8) +#define SAI_RCR4_SYWD_MASK (0x1f << 8) +#define SAI_RCR4_MF (1 << 4) +/* Frame sync is active low */ +#define SAI_RCR4_FSE (1 << 3) +#define SAI_RCR4_FSP (1 << 1) +#define SAI_RCR4_FSD_MSTR (1 << 0) + +#define SAI_RCR5 0x94 +#define SAI_RCR5_WNW(x) (x << 24) +#define SAI_RCR5_WNW_MASK (0x1f << 24) +#define SAI_RCR5_W0W(x) (x << 16) +#define SAI_RCR5_W0W_MASK (0x1f << 16) + +#define SAI_RDR 0xa0 + +#define SAI_RFR 0xc0 + +#define SAI_RMR 0xe0 + +/* SAI clock sources */ +#define MVF_SAI_BUS_CLK 0 +#define MVF_SAI_MAST_CLK1 1 +#define MVF_SAI_MAST_CLK2 2 +#define MVF_SAI_MAST_CLK3 3 + +/* SAI audio dividers */ +#define MVF_SAI_TX_DIV 0 +#define MVF_SAI_RX_DIV 1 + +#define DRV_NAME "mvf-sai" + +#include <linux/dmaengine.h> +#include <mach/dma.h> + +struct mvf_sai { + struct clk *clk; + void __iomem *base; + int irq; + int fiq_enable; + unsigned int offset; + + unsigned int flags; + + struct imx_pcm_dma_params dma_params_rx; + struct imx_pcm_dma_params dma_params_tx; + + int play_enabled; + int cap_enabled; + + struct platform_device *soc_platform_pdev; + struct platform_device *soc_platform_pdev_fiq; +}; + +int snd_mvf_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma); +int mvf_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm); +void mvf_pcm_free(struct snd_pcm *pcm); + +#define MVF_SAI_DMABUF_SIZE (32 * 1024) +#define TCD_NUMBER 4 + +#endif /* _MVF_SAI_H */ diff --git a/sound/soc/mvf/mvf-sgtl5000.c b/sound/soc/mvf/mvf-sgtl5000.c new file mode 100644 index 000000000000..a5fb36038f66 --- /dev/null +++ b/sound/soc/mvf/mvf-sgtl5000.c @@ -0,0 +1,320 @@ +/* + * sound/soc/mvf-sgtl5000.c -- SoC audio for Faraday TWR-AUDIO-SGTL boards + * with sgtl5000 codec + * + * Copyright 2012 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. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/fsl_devices.h> +#include <linux/gpio.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <asm/mach-types.h> + +#include "../codecs/sgtl5000.h" +#include "mvf-sai.h" + + +static struct mvf_sgtl5000_priv { + int sysclk; + int hw; + struct platform_device *pdev; +} card_priv; + +static struct snd_soc_card mvf_sgtl5000; + +static int sgtl5000_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; + u32 dai_format; + int ret; + unsigned int channels = params_channels(params); + + snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, card_priv.sysclk, 1); + + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret < 0) + return ret; + + /* TODO: The SAI driver should figure this out for us */ + switch (channels) { + case 2: + snd_soc_dai_set_tdm_slot(cpu_dai, 0xfffffffc, 0xfffffffc, 2, 0); + break; + case 1: + snd_soc_dai_set_tdm_slot(cpu_dai, 0xfffffffe, 0xfffffffe, 1, 0); + break; + default: + return -EINVAL; + } + + /* set cpu DAI configuration */ + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBM_CFM; + ret = snd_soc_dai_set_fmt(cpu_dai, dai_format); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops mvf_sgtl5000_hifi_ops = { + .hw_params = sgtl5000_params, +}; + +static int sgtl5000_jack_func; +static int sgtl5000_spk_func; +static int sgtl5000_line_in_func; + +static const char * const jack_function[] = { "off", "on"}; + +static const char * const spk_function[] = { "off", "on" }; + +static const char * const line_in_function[] = { "off", "on" }; + +static const struct soc_enum sgtl5000_enum[] = { + SOC_ENUM_SINGLE_EXT(2, jack_function), + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, line_in_function), +}; + +static int sgtl5000_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = sgtl5000_jack_func; + return 0; +} + +static int sgtl5000_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (sgtl5000_jack_func == ucontrol->value.enumerated.item[0]) + return 0; + + sgtl5000_jack_func = ucontrol->value.enumerated.item[0]; + if (sgtl5000_jack_func) + snd_soc_dapm_enable_pin(&codec->dapm, "Headphone Jack"); + else + snd_soc_dapm_disable_pin(&codec->dapm, "Headphone Jack"); + + snd_soc_dapm_sync(&codec->dapm); + return 1; +} + +static int sgtl5000_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = sgtl5000_spk_func; + return 0; +} + +static int sgtl5000_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (sgtl5000_spk_func == ucontrol->value.enumerated.item[0]) + return 0; + + sgtl5000_spk_func = ucontrol->value.enumerated.item[0]; + if (sgtl5000_spk_func) + snd_soc_dapm_enable_pin(&codec->dapm, "Ext Spk"); + else + snd_soc_dapm_disable_pin(&codec->dapm, "Ext Spk"); + + snd_soc_dapm_sync(&codec->dapm); + return 1; +} + +static int sgtl5000_get_line_in(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = sgtl5000_line_in_func; + return 0; +} + +static int sgtl5000_set_line_in(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (sgtl5000_line_in_func == ucontrol->value.enumerated.item[0]) + return 0; + + sgtl5000_line_in_func = ucontrol->value.enumerated.item[0]; + if (sgtl5000_line_in_func) + snd_soc_dapm_enable_pin(&codec->dapm, "Line In Jack"); + else + snd_soc_dapm_disable_pin(&codec->dapm, "Line In Jack"); + + snd_soc_dapm_sync(&codec->dapm); + return 1; +} + +static const struct snd_soc_dapm_widget mvf_twr_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +static const struct snd_kcontrol_new sgtl5000_machine_controls[] = { + SOC_ENUM_EXT("Jack Function", sgtl5000_enum[0], sgtl5000_get_jack, + sgtl5000_set_jack), + SOC_ENUM_EXT("Speaker Function", sgtl5000_enum[1], sgtl5000_get_spk, + sgtl5000_set_spk), + SOC_ENUM_EXT("Line In Function", sgtl5000_enum[1], sgtl5000_get_line_in, + sgtl5000_set_line_in), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + + /* Mic Jack --> MIC_IN (with automatic bias) */ + {"MIC_IN", NULL, "Mic Jack"}, + + /* Line in Jack --> LINE_IN */ + {"LINE_IN", NULL, "Line In Jack"}, + + /* HP_OUT --> Headphone Jack */ + {"Headphone Jack", NULL, "HP_OUT"}, + + /* LINE_OUT --> Ext Speaker */ + {"Ext Spk", NULL, "LINE_OUT"}, +}; + +static int mvf_twr_sgtl5000_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + int ret; + + ret = snd_soc_add_controls(codec, sgtl5000_machine_controls, + ARRAY_SIZE(sgtl5000_machine_controls)); + if (ret) + return ret; + + /* Add mvf_twr specific widgets */ + snd_soc_dapm_new_controls(&codec->dapm, mvf_twr_dapm_widgets, + ARRAY_SIZE(mvf_twr_dapm_widgets)); + + /* Set up mvf_twr specific audio path audio_map */ + snd_soc_dapm_add_routes(&codec->dapm, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_disable_pin(&codec->dapm, "Line In Jack"); + snd_soc_dapm_enable_pin(&codec->dapm, "Headphone Jack"); + snd_soc_dapm_sync(&codec->dapm); + + return 0; +} + +static struct snd_soc_dai_link mvf_sgtl5000_dai[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .codec_dai_name = "sgtl5000", + .codec_name = "sgtl5000.0-000a", + .cpu_dai_name = "mvf-sai.0", + .platform_name = "mvf-pcm-audio.0", + .init = mvf_twr_sgtl5000_init, + .ops = &mvf_sgtl5000_hifi_ops, + }, +}; + +static struct snd_soc_card mvf_sgtl5000 = { + .name = "sgtl5000-sai", + .dai_link = mvf_sgtl5000_dai, + .num_links = ARRAY_SIZE(mvf_sgtl5000_dai), +}; + +static struct platform_device *mvf_sgtl5000_snd_device; + +static int __devinit mvf_sgtl5000_probe(struct platform_device *pdev) +{ + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + + card_priv.pdev = pdev; + + if (plat->init && plat->init()) + return -EINVAL; + + card_priv.sysclk = 24576000; + + return 0; +} + +static int mvf_sgtl5000_remove(struct platform_device *pdev) +{ + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + + if (plat->finit) + plat->finit(); + + return 0; +} + +static struct platform_driver mvf_sgtl5000_audio_driver = { + .probe = mvf_sgtl5000_probe, + .remove = mvf_sgtl5000_remove, + .driver = { + .name = "mvf-sgtl5000", + .owner = THIS_MODULE, + }, +}; + +static int __init mvf_sgtl5000_init(void) +{ + int ret; + + ret = platform_driver_register(&mvf_sgtl5000_audio_driver); + if (ret) + return -ENOMEM; + + mvf_sgtl5000_dai[0].codec_name = "sgtl5000.0-000a"; + + mvf_sgtl5000_snd_device = platform_device_alloc("soc-audio", 1); + if (!mvf_sgtl5000_snd_device) + return -ENOMEM; + + platform_set_drvdata(mvf_sgtl5000_snd_device, &mvf_sgtl5000); + + ret = platform_device_add(mvf_sgtl5000_snd_device); + + if (ret) { + printk(KERN_ERR "ASoC: Platform device allocation failed\n"); + platform_device_put(mvf_sgtl5000_snd_device); + } + + return ret; +} + +static void __exit mvf_sgtl5000_exit(void) +{ + platform_driver_unregister(&mvf_sgtl5000_audio_driver); + platform_device_unregister(mvf_sgtl5000_snd_device); +} + +module_init(mvf_sgtl5000_init); +module_exit(mvf_sgtl5000_exit); + +MODULE_AUTHOR("Alison Wang, <b18965@freescale.com>"); +MODULE_DESCRIPTION("PhyCORE ALSA SoC driver"); +MODULE_LICENSE("GPL"); |