diff options
| author | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
|---|---|---|
| committer | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
| commit | fcc9d2e5a6c89d22b8b773a64fb4ad21ac318446 (patch) | |
| tree | a57612d1888735a2ec7972891b68c1ac5ec8faea /sound/soc/imx | |
| parent | 8dea78da5cee153b8af9c07a2745f6c55057fe12 (diff) | |
Diffstat (limited to 'sound/soc/imx')
| -rw-r--r-- | sound/soc/imx/Kconfig | 59 | ||||
| -rw-r--r-- | sound/soc/imx/Makefile | 19 | ||||
| -rw-r--r-- | sound/soc/imx/eukrea-tlv320.c | 131 | ||||
| -rw-r--r-- | sound/soc/imx/imx-pcm-dma-mx2.c | 341 | ||||
| -rw-r--r-- | sound/soc/imx/imx-pcm-fiq.c | 346 | ||||
| -rw-r--r-- | sound/soc/imx/imx-ssi.c | 778 | ||||
| -rw-r--r-- | sound/soc/imx/imx-ssi.h | 236 | ||||
| -rw-r--r-- | sound/soc/imx/mx27vis-aic32x4.c | 137 | ||||
| -rw-r--r-- | sound/soc/imx/phycore-ac97.c | 99 | ||||
| -rw-r--r-- | sound/soc/imx/wm1133-ev1.c | 303 |
10 files changed, 2449 insertions, 0 deletions
diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig new file mode 100644 index 00000000000..bb699bb55a5 --- /dev/null +++ b/sound/soc/imx/Kconfig | |||
| @@ -0,0 +1,59 @@ | |||
| 1 | menuconfig SND_IMX_SOC | ||
| 2 | tristate "SoC Audio for Freescale i.MX CPUs" | ||
| 3 | depends on ARCH_MXC | ||
| 4 | select SND_PCM | ||
| 5 | select FIQ | ||
| 6 | select SND_SOC_AC97_BUS | ||
| 7 | help | ||
| 8 | Say Y or M if you want to add support for codecs attached to | ||
| 9 | the i.MX SSI interface. | ||
| 10 | |||
| 11 | |||
| 12 | if SND_IMX_SOC | ||
| 13 | |||
| 14 | config SND_MXC_SOC_FIQ | ||
| 15 | tristate | ||
| 16 | |||
| 17 | config SND_MXC_SOC_MX2 | ||
| 18 | tristate | ||
| 19 | |||
| 20 | config SND_MXC_SOC_WM1133_EV1 | ||
| 21 | tristate "Audio on the the i.MX31ADS with WM1133-EV1 fitted" | ||
| 22 | depends on MACH_MX31ADS_WM1133_EV1 && EXPERIMENTAL | ||
| 23 | select SND_SOC_WM8350 | ||
| 24 | select SND_MXC_SOC_FIQ | ||
| 25 | help | ||
| 26 | Enable support for audio on the i.MX31ADS with the WM1133-EV1 | ||
| 27 | PMIC board with WM8835x fitted. | ||
| 28 | |||
| 29 | config SND_SOC_MX27VIS_AIC32X4 | ||
| 30 | tristate "SoC audio support for Visstrim M10 boards" | ||
| 31 | depends on MACH_IMX27_VISSTRIM_M10 | ||
| 32 | select SND_SOC_TVL320AIC32X4 | ||
| 33 | select SND_MXC_SOC_MX2 | ||
| 34 | help | ||
| 35 | Say Y if you want to add support for SoC audio on Visstrim SM10 | ||
| 36 | board with TLV320AIC32X4 codec. | ||
| 37 | |||
| 38 | config SND_SOC_PHYCORE_AC97 | ||
| 39 | tristate "SoC Audio support for Phytec phyCORE (and phyCARD) boards" | ||
| 40 | depends on MACH_PCM043 || MACH_PCA100 | ||
| 41 | select SND_SOC_WM9712 | ||
| 42 | select SND_MXC_SOC_FIQ | ||
| 43 | help | ||
| 44 | Say Y if you want to add support for SoC audio on Phytec phyCORE | ||
| 45 | and phyCARD boards in AC97 mode | ||
| 46 | |||
| 47 | config SND_SOC_EUKREA_TLV320 | ||
| 48 | tristate "Eukrea TLV320" | ||
| 49 | depends on MACH_EUKREA_MBIMX27_BASEBOARD \ | ||
| 50 | || MACH_EUKREA_MBIMXSD25_BASEBOARD \ | ||
| 51 | || MACH_EUKREA_MBIMXSD35_BASEBOARD \ | ||
| 52 | || MACH_EUKREA_MBIMXSD51_BASEBOARD | ||
| 53 | select SND_SOC_TLV320AIC23 | ||
| 54 | select SND_MXC_SOC_FIQ | ||
| 55 | help | ||
| 56 | Enable I2S based access to the TLV320AIC23B codec attached | ||
| 57 | to the SSI interface | ||
| 58 | |||
| 59 | endif # SND_IMX_SOC | ||
diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile new file mode 100644 index 00000000000..d6d609ba7e2 --- /dev/null +++ b/sound/soc/imx/Makefile | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | # i.MX Platform Support | ||
| 2 | snd-soc-imx-objs := imx-ssi.o | ||
| 3 | snd-soc-imx-fiq-objs := imx-pcm-fiq.o | ||
| 4 | snd-soc-imx-mx2-objs := imx-pcm-dma-mx2.o | ||
| 5 | |||
| 6 | obj-$(CONFIG_SND_IMX_SOC) += snd-soc-imx.o | ||
| 7 | obj-$(CONFIG_SND_MXC_SOC_FIQ) += snd-soc-imx-fiq.o | ||
| 8 | obj-$(CONFIG_SND_MXC_SOC_MX2) += snd-soc-imx-mx2.o | ||
| 9 | |||
| 10 | # i.MX Machine Support | ||
| 11 | snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o | ||
| 12 | snd-soc-phycore-ac97-objs := phycore-ac97.o | ||
| 13 | snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o | ||
| 14 | snd-soc-wm1133-ev1-objs := wm1133-ev1.o | ||
| 15 | |||
| 16 | obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o | ||
| 17 | obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o | ||
| 18 | obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o | ||
| 19 | obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o | ||
diff --git a/sound/soc/imx/eukrea-tlv320.c b/sound/soc/imx/eukrea-tlv320.c new file mode 100644 index 00000000000..75fb4b83548 --- /dev/null +++ b/sound/soc/imx/eukrea-tlv320.c | |||
| @@ -0,0 +1,131 @@ | |||
| 1 | /* | ||
| 2 | * eukrea-tlv320.c -- SoC audio for eukrea_cpuimxXX in I2S mode | ||
| 3 | * | ||
| 4 | * Copyright 2010 Eric Bénard, Eukréa Electromatique <eric@eukrea.com> | ||
| 5 | * | ||
| 6 | * based on sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c | ||
| 7 | * which is Copyright 2009 Simtec Electronics | ||
| 8 | * and on sound/soc/imx/phycore-ac97.c which is | ||
| 9 | * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> | ||
| 10 | * | ||
| 11 | * This program is free software; you can redistribute it and/or modify it | ||
| 12 | * under the terms of the GNU General Public License as published by the | ||
| 13 | * Free Software Foundation; either version 2 of the License, or (at your | ||
| 14 | * option) any later version. | ||
| 15 | * | ||
| 16 | */ | ||
| 17 | |||
| 18 | #include <linux/module.h> | ||
| 19 | #include <linux/moduleparam.h> | ||
| 20 | #include <linux/device.h> | ||
| 21 | #include <linux/i2c.h> | ||
| 22 | #include <sound/core.h> | ||
| 23 | #include <sound/pcm.h> | ||
| 24 | #include <sound/soc.h> | ||
| 25 | #include <asm/mach-types.h> | ||
| 26 | |||
| 27 | #include "../codecs/tlv320aic23.h" | ||
| 28 | #include "imx-ssi.h" | ||
| 29 | |||
| 30 | #define CODEC_CLOCK 12000000 | ||
| 31 | |||
| 32 | static int eukrea_tlv320_hw_params(struct snd_pcm_substream *substream, | ||
| 33 | struct snd_pcm_hw_params *params) | ||
| 34 | { | ||
| 35 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
| 36 | struct snd_soc_dai *codec_dai = rtd->codec_dai; | ||
| 37 | struct snd_soc_dai *cpu_dai = rtd->cpu_dai; | ||
| 38 | int ret; | ||
| 39 | |||
| 40 | ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | | ||
| 41 | SND_SOC_DAIFMT_NB_NF | | ||
| 42 | SND_SOC_DAIFMT_CBM_CFM); | ||
| 43 | if (ret) { | ||
| 44 | pr_err("%s: failed set cpu dai format\n", __func__); | ||
| 45 | return ret; | ||
| 46 | } | ||
| 47 | |||
| 48 | ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | | ||
| 49 | SND_SOC_DAIFMT_NB_NF | | ||
| 50 | SND_SOC_DAIFMT_CBM_CFM); | ||
| 51 | if (ret) { | ||
| 52 | pr_err("%s: failed set codec dai format\n", __func__); | ||
| 53 | return ret; | ||
| 54 | } | ||
| 55 | |||
| 56 | ret = snd_soc_dai_set_sysclk(codec_dai, 0, | ||
| 57 | CODEC_CLOCK, SND_SOC_CLOCK_OUT); | ||
| 58 | if (ret) { | ||
| 59 | pr_err("%s: failed setting codec sysclk\n", __func__); | ||
| 60 | return ret; | ||
| 61 | } | ||
| 62 | snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffc, 0xffffffc, 2, 0); | ||
| 63 | |||
| 64 | ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, | ||
| 65 | SND_SOC_CLOCK_IN); | ||
| 66 | if (ret) { | ||
| 67 | pr_err("can't set CPU system clock IMX_SSP_SYS_CLK\n"); | ||
| 68 | return ret; | ||
| 69 | } | ||
| 70 | |||
| 71 | return 0; | ||
| 72 | } | ||
| 73 | |||
| 74 | static struct snd_soc_ops eukrea_tlv320_snd_ops = { | ||
| 75 | .hw_params = eukrea_tlv320_hw_params, | ||
| 76 | }; | ||
| 77 | |||
| 78 | static struct snd_soc_dai_link eukrea_tlv320_dai = { | ||
| 79 | .name = "tlv320aic23", | ||
| 80 | .stream_name = "TLV320AIC23", | ||
| 81 | .codec_dai_name = "tlv320aic23-hifi", | ||
| 82 | .platform_name = "imx-fiq-pcm-audio.0", | ||
| 83 | .codec_name = "tlv320aic23-codec.0-001a", | ||
| 84 | .cpu_dai_name = "imx-ssi.0", | ||
| 85 | .ops = &eukrea_tlv320_snd_ops, | ||
| 86 | }; | ||
| 87 | |||
| 88 | static struct snd_soc_card eukrea_tlv320 = { | ||
| 89 | .name = "cpuimx-audio", | ||
| 90 | .dai_link = &eukrea_tlv320_dai, | ||
| 91 | .num_links = 1, | ||
| 92 | }; | ||
| 93 | |||
| 94 | static struct platform_device *eukrea_tlv320_snd_device; | ||
| 95 | |||
| 96 | static int __init eukrea_tlv320_init(void) | ||
| 97 | { | ||
| 98 | int ret; | ||
| 99 | |||
| 100 | if (!machine_is_eukrea_cpuimx27() && !machine_is_eukrea_cpuimx25sd() | ||
| 101 | && !machine_is_eukrea_cpuimx35sd() | ||
| 102 | && !machine_is_eukrea_cpuimx51sd()) | ||
| 103 | /* return happy. We might run on a totally different machine */ | ||
| 104 | return 0; | ||
| 105 | |||
| 106 | eukrea_tlv320_snd_device = platform_device_alloc("soc-audio", -1); | ||
| 107 | if (!eukrea_tlv320_snd_device) | ||
| 108 | return -ENOMEM; | ||
| 109 | |||
| 110 | platform_set_drvdata(eukrea_tlv320_snd_device, &eukrea_tlv320); | ||
| 111 | ret = platform_device_add(eukrea_tlv320_snd_device); | ||
| 112 | |||
| 113 | if (ret) { | ||
| 114 | printk(KERN_ERR "ASoC: Platform device allocation failed\n"); | ||
| 115 | platform_device_put(eukrea_tlv320_snd_device); | ||
| 116 | } | ||
| 117 | |||
| 118 | return ret; | ||
| 119 | } | ||
| 120 | |||
| 121 | static void __exit eukrea_tlv320_exit(void) | ||
| 122 | { | ||
| 123 | platform_device_unregister(eukrea_tlv320_snd_device); | ||
| 124 | } | ||
| 125 | |||
| 126 | module_init(eukrea_tlv320_init); | ||
| 127 | module_exit(eukrea_tlv320_exit); | ||
| 128 | |||
| 129 | MODULE_AUTHOR("Eric Bénard <eric@eukrea.com>"); | ||
| 130 | MODULE_DESCRIPTION("CPUIMX ALSA SoC driver"); | ||
| 131 | MODULE_LICENSE("GPL"); | ||
diff --git a/sound/soc/imx/imx-pcm-dma-mx2.c b/sound/soc/imx/imx-pcm-dma-mx2.c new file mode 100644 index 00000000000..43fdc24f7e8 --- /dev/null +++ b/sound/soc/imx/imx-pcm-dma-mx2.c | |||
| @@ -0,0 +1,341 @@ | |||
| 1 | /* | ||
| 2 | * imx-pcm-dma-mx2.c -- ALSA Soc Audio Layer | ||
| 3 | * | ||
| 4 | * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> | ||
| 5 | * | ||
| 6 | * This code is based on code copyrighted by Freescale, | ||
| 7 | * Liam Girdwood, Javier Martin and probably others. | ||
| 8 | * | ||
| 9 | * This program is free software; you can redistribute it and/or modify it | ||
| 10 | * under the terms of the GNU General Public License as published by the | ||
| 11 | * Free Software Foundation; either version 2 of the License, or (at your | ||
| 12 | * option) any later version. | ||
| 13 | */ | ||
| 14 | #include <linux/clk.h> | ||
| 15 | #include <linux/delay.h> | ||
| 16 | #include <linux/device.h> | ||
| 17 | #include <linux/dma-mapping.h> | ||
| 18 | #include <linux/init.h> | ||
| 19 | #include <linux/interrupt.h> | ||
| 20 | #include <linux/module.h> | ||
| 21 | #include <linux/platform_device.h> | ||
| 22 | #include <linux/slab.h> | ||
| 23 | #include <linux/dmaengine.h> | ||
| 24 | |||
| 25 | #include <sound/core.h> | ||
| 26 | #include <sound/initval.h> | ||
| 27 | #include <sound/pcm.h> | ||
| 28 | #include <sound/pcm_params.h> | ||
| 29 | #include <sound/soc.h> | ||
| 30 | |||
| 31 | #include <mach/dma.h> | ||
| 32 | |||
| 33 | #include "imx-ssi.h" | ||
| 34 | |||
| 35 | struct imx_pcm_runtime_data { | ||
| 36 | int period_bytes; | ||
| 37 | int periods; | ||
| 38 | int dma; | ||
| 39 | unsigned long offset; | ||
| 40 | unsigned long size; | ||
| 41 | void *buf; | ||
| 42 | int period_time; | ||
| 43 | struct dma_async_tx_descriptor *desc; | ||
| 44 | struct dma_chan *dma_chan; | ||
| 45 | struct imx_dma_data dma_data; | ||
| 46 | }; | ||
| 47 | |||
| 48 | static void audio_dma_irq(void *data) | ||
| 49 | { | ||
| 50 | struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data; | ||
| 51 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 52 | struct imx_pcm_runtime_data *iprtd = runtime->private_data; | ||
| 53 | |||
| 54 | iprtd->offset += iprtd->period_bytes; | ||
| 55 | iprtd->offset %= iprtd->period_bytes * iprtd->periods; | ||
| 56 | |||
| 57 | snd_pcm_period_elapsed(substream); | ||
| 58 | } | ||
| 59 | |||
| 60 | static bool filter(struct dma_chan *chan, void *param) | ||
| 61 | { | ||
| 62 | struct imx_pcm_runtime_data *iprtd = param; | ||
| 63 | |||
| 64 | if (!imx_dma_is_general_purpose(chan)) | ||
| 65 | return false; | ||
| 66 | |||
| 67 | chan->private = &iprtd->dma_data; | ||
| 68 | |||
| 69 | return true; | ||
| 70 | } | ||
| 71 | |||
| 72 | static int imx_ssi_dma_alloc(struct snd_pcm_substream *substream, | ||
| 73 | struct snd_pcm_hw_params *params) | ||
| 74 | { | ||
| 75 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
| 76 | struct imx_pcm_dma_params *dma_params; | ||
| 77 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 78 | struct imx_pcm_runtime_data *iprtd = runtime->private_data; | ||
| 79 | struct dma_slave_config slave_config; | ||
| 80 | dma_cap_mask_t mask; | ||
| 81 | enum dma_slave_buswidth buswidth; | ||
| 82 | int ret; | ||
| 83 | |||
| 84 | dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); | ||
| 85 | |||
| 86 | iprtd->dma_data.peripheral_type = IMX_DMATYPE_SSI; | ||
| 87 | iprtd->dma_data.priority = DMA_PRIO_HIGH; | ||
| 88 | iprtd->dma_data.dma_request = dma_params->dma; | ||
| 89 | |||
| 90 | /* Try to grab a DMA channel */ | ||
| 91 | dma_cap_zero(mask); | ||
| 92 | dma_cap_set(DMA_SLAVE, mask); | ||
| 93 | iprtd->dma_chan = dma_request_channel(mask, filter, iprtd); | ||
| 94 | if (!iprtd->dma_chan) | ||
| 95 | return -EINVAL; | ||
| 96 | |||
| 97 | switch (params_format(params)) { | ||
| 98 | case SNDRV_PCM_FORMAT_S16_LE: | ||
| 99 | buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; | ||
| 100 | break; | ||
| 101 | case SNDRV_PCM_FORMAT_S20_3LE: | ||
| 102 | case SNDRV_PCM_FORMAT_S24_LE: | ||
| 103 | buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; | ||
| 104 | break; | ||
| 105 | default: | ||
| 106 | return 0; | ||
| 107 | } | ||
| 108 | |||
| 109 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
| 110 | slave_config.direction = DMA_TO_DEVICE; | ||
| 111 | slave_config.dst_addr = dma_params->dma_addr; | ||
| 112 | slave_config.dst_addr_width = buswidth; | ||
| 113 | slave_config.dst_maxburst = dma_params->burstsize; | ||
| 114 | } else { | ||
| 115 | slave_config.direction = DMA_FROM_DEVICE; | ||
| 116 | slave_config.src_addr = dma_params->dma_addr; | ||
| 117 | slave_config.src_addr_width = buswidth; | ||
| 118 | slave_config.src_maxburst = dma_params->burstsize; | ||
| 119 | } | ||
| 120 | |||
| 121 | ret = dmaengine_slave_config(iprtd->dma_chan, &slave_config); | ||
| 122 | if (ret) | ||
| 123 | return ret; | ||
| 124 | |||
| 125 | return 0; | ||
| 126 | } | ||
| 127 | |||
| 128 | static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream, | ||
| 129 | struct snd_pcm_hw_params *params) | ||
| 130 | { | ||
| 131 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
| 132 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 133 | struct imx_pcm_runtime_data *iprtd = runtime->private_data; | ||
| 134 | unsigned long dma_addr; | ||
| 135 | struct dma_chan *chan; | ||
| 136 | struct imx_pcm_dma_params *dma_params; | ||
| 137 | int ret; | ||
| 138 | |||
| 139 | dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); | ||
| 140 | ret = imx_ssi_dma_alloc(substream, params); | ||
| 141 | if (ret) | ||
| 142 | return ret; | ||
| 143 | chan = iprtd->dma_chan; | ||
| 144 | |||
| 145 | iprtd->size = params_buffer_bytes(params); | ||
| 146 | iprtd->periods = params_periods(params); | ||
| 147 | iprtd->period_bytes = params_period_bytes(params); | ||
| 148 | iprtd->offset = 0; | ||
| 149 | iprtd->period_time = HZ / (params_rate(params) / | ||
| 150 | params_period_size(params)); | ||
| 151 | |||
| 152 | snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); | ||
| 153 | |||
| 154 | dma_addr = runtime->dma_addr; | ||
| 155 | |||
| 156 | iprtd->buf = (unsigned int *)substream->dma_buffer.area; | ||
| 157 | |||
| 158 | iprtd->desc = chan->device->device_prep_dma_cyclic(chan, dma_addr, | ||
| 159 | iprtd->period_bytes * iprtd->periods, | ||
| 160 | iprtd->period_bytes, | ||
| 161 | substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? | ||
| 162 | DMA_TO_DEVICE : DMA_FROM_DEVICE); | ||
| 163 | if (!iprtd->desc) { | ||
| 164 | dev_err(&chan->dev->device, "cannot prepare slave dma\n"); | ||
| 165 | return -EINVAL; | ||
| 166 | } | ||
| 167 | |||
| 168 | iprtd->desc->callback = audio_dma_irq; | ||
| 169 | iprtd->desc->callback_param = substream; | ||
| 170 | |||
| 171 | return 0; | ||
| 172 | } | ||
| 173 | |||
| 174 | static int snd_imx_pcm_hw_free(struct snd_pcm_substream *substream) | ||
| 175 | { | ||
| 176 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 177 | struct imx_pcm_runtime_data *iprtd = runtime->private_data; | ||
| 178 | |||
| 179 | if (iprtd->dma_chan) { | ||
| 180 | dma_release_channel(iprtd->dma_chan); | ||
| 181 | iprtd->dma_chan = NULL; | ||
| 182 | } | ||
| 183 | |||
| 184 | return 0; | ||
| 185 | } | ||
| 186 | |||
| 187 | static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream) | ||
| 188 | { | ||
| 189 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
| 190 | struct imx_pcm_dma_params *dma_params; | ||
| 191 | |||
| 192 | dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); | ||
| 193 | |||
| 194 | return 0; | ||
| 195 | } | ||
| 196 | |||
| 197 | static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | ||
| 198 | { | ||
| 199 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 200 | struct imx_pcm_runtime_data *iprtd = runtime->private_data; | ||
| 201 | |||
| 202 | switch (cmd) { | ||
| 203 | case SNDRV_PCM_TRIGGER_START: | ||
| 204 | case SNDRV_PCM_TRIGGER_RESUME: | ||
| 205 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
| 206 | dmaengine_submit(iprtd->desc); | ||
| 207 | |||
| 208 | break; | ||
| 209 | |||
| 210 | case SNDRV_PCM_TRIGGER_STOP: | ||
| 211 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
| 212 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
| 213 | dmaengine_terminate_all(iprtd->dma_chan); | ||
| 214 | |||
| 215 | break; | ||
| 216 | default: | ||
| 217 | return -EINVAL; | ||
| 218 | } | ||
| 219 | |||
| 220 | return 0; | ||
| 221 | } | ||
| 222 | |||
| 223 | static snd_pcm_uframes_t snd_imx_pcm_pointer(struct snd_pcm_substream *substream) | ||
| 224 | { | ||
| 225 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 226 | struct imx_pcm_runtime_data *iprtd = runtime->private_data; | ||
| 227 | |||
| 228 | pr_debug("%s: %ld %ld\n", __func__, iprtd->offset, | ||
| 229 | bytes_to_frames(substream->runtime, iprtd->offset)); | ||
| 230 | |||
| 231 | return bytes_to_frames(substream->runtime, iprtd->offset); | ||
| 232 | } | ||
| 233 | |||
| 234 | static struct snd_pcm_hardware snd_imx_hardware = { | ||
| 235 | .info = SNDRV_PCM_INFO_INTERLEAVED | | ||
| 236 | SNDRV_PCM_INFO_BLOCK_TRANSFER | | ||
| 237 | SNDRV_PCM_INFO_MMAP | | ||
| 238 | SNDRV_PCM_INFO_MMAP_VALID | | ||
| 239 | SNDRV_PCM_INFO_PAUSE | | ||
| 240 | SNDRV_PCM_INFO_RESUME, | ||
| 241 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
| 242 | .rate_min = 8000, | ||
| 243 | .channels_min = 2, | ||
| 244 | .channels_max = 2, | ||
| 245 | .buffer_bytes_max = IMX_SSI_DMABUF_SIZE, | ||
| 246 | .period_bytes_min = 128, | ||
| 247 | .period_bytes_max = 65535, /* Limited by SDMA engine */ | ||
| 248 | .periods_min = 2, | ||
| 249 | .periods_max = 255, | ||
| 250 | .fifo_size = 0, | ||
| 251 | }; | ||
| 252 | |||
| 253 | static int snd_imx_open(struct snd_pcm_substream *substream) | ||
| 254 | { | ||
| 255 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 256 | struct imx_pcm_runtime_data *iprtd; | ||
| 257 | int ret; | ||
| 258 | |||
| 259 | iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL); | ||
| 260 | if (iprtd == NULL) | ||
| 261 | return -ENOMEM; | ||
| 262 | runtime->private_data = iprtd; | ||
| 263 | |||
| 264 | ret = snd_pcm_hw_constraint_integer(substream->runtime, | ||
| 265 | SNDRV_PCM_HW_PARAM_PERIODS); | ||
| 266 | if (ret < 0) { | ||
| 267 | kfree(iprtd); | ||
| 268 | return ret; | ||
| 269 | } | ||
| 270 | |||
| 271 | snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); | ||
| 272 | |||
| 273 | return 0; | ||
| 274 | } | ||
| 275 | |||
| 276 | static int snd_imx_close(struct snd_pcm_substream *substream) | ||
| 277 | { | ||
| 278 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 279 | struct imx_pcm_runtime_data *iprtd = runtime->private_data; | ||
| 280 | |||
| 281 | kfree(iprtd); | ||
| 282 | |||
| 283 | return 0; | ||
| 284 | } | ||
| 285 | |||
| 286 | static struct snd_pcm_ops imx_pcm_ops = { | ||
| 287 | .open = snd_imx_open, | ||
| 288 | .close = snd_imx_close, | ||
| 289 | .ioctl = snd_pcm_lib_ioctl, | ||
| 290 | .hw_params = snd_imx_pcm_hw_params, | ||
| 291 | .hw_free = snd_imx_pcm_hw_free, | ||
| 292 | .prepare = snd_imx_pcm_prepare, | ||
| 293 | .trigger = snd_imx_pcm_trigger, | ||
| 294 | .pointer = snd_imx_pcm_pointer, | ||
| 295 | .mmap = snd_imx_pcm_mmap, | ||
| 296 | }; | ||
| 297 | |||
| 298 | static struct snd_soc_platform_driver imx_soc_platform_mx2 = { | ||
| 299 | .ops = &imx_pcm_ops, | ||
| 300 | .pcm_new = imx_pcm_new, | ||
| 301 | .pcm_free = imx_pcm_free, | ||
| 302 | }; | ||
| 303 | |||
| 304 | static int __devinit imx_soc_platform_probe(struct platform_device *pdev) | ||
| 305 | { | ||
| 306 | struct imx_ssi *ssi = platform_get_drvdata(pdev); | ||
| 307 | |||
| 308 | ssi->dma_params_tx.burstsize = 6; | ||
| 309 | ssi->dma_params_rx.burstsize = 4; | ||
| 310 | |||
| 311 | return snd_soc_register_platform(&pdev->dev, &imx_soc_platform_mx2); | ||
| 312 | } | ||
| 313 | |||
| 314 | static int __devexit imx_soc_platform_remove(struct platform_device *pdev) | ||
| 315 | { | ||
| 316 | snd_soc_unregister_platform(&pdev->dev); | ||
| 317 | return 0; | ||
| 318 | } | ||
| 319 | |||
| 320 | static struct platform_driver imx_pcm_driver = { | ||
| 321 | .driver = { | ||
| 322 | .name = "imx-pcm-audio", | ||
| 323 | .owner = THIS_MODULE, | ||
| 324 | }, | ||
| 325 | .probe = imx_soc_platform_probe, | ||
| 326 | .remove = __devexit_p(imx_soc_platform_remove), | ||
| 327 | }; | ||
| 328 | |||
| 329 | static int __init snd_imx_pcm_init(void) | ||
| 330 | { | ||
| 331 | return platform_driver_register(&imx_pcm_driver); | ||
| 332 | } | ||
| 333 | module_init(snd_imx_pcm_init); | ||
| 334 | |||
| 335 | static void __exit snd_imx_pcm_exit(void) | ||
| 336 | { | ||
| 337 | platform_driver_unregister(&imx_pcm_driver); | ||
| 338 | } | ||
| 339 | module_exit(snd_imx_pcm_exit); | ||
| 340 | MODULE_LICENSE("GPL"); | ||
| 341 | MODULE_ALIAS("platform:imx-pcm-audio"); | ||
diff --git a/sound/soc/imx/imx-pcm-fiq.c b/sound/soc/imx/imx-pcm-fiq.c new file mode 100644 index 00000000000..7945625e0e0 --- /dev/null +++ b/sound/soc/imx/imx-pcm-fiq.c | |||
| @@ -0,0 +1,346 @@ | |||
| 1 | /* | ||
| 2 | * imx-pcm-fiq.c -- ALSA Soc Audio Layer | ||
| 3 | * | ||
| 4 | * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> | ||
| 5 | * | ||
| 6 | * This code is based on code copyrighted by Freescale, | ||
| 7 | * Liam Girdwood, Javier Martin and probably others. | ||
| 8 | * | ||
| 9 | * This program is free software; you can redistribute it and/or modify it | ||
| 10 | * under the terms of the GNU General Public License as published by the | ||
| 11 | * Free Software Foundation; either version 2 of the License, or (at your | ||
| 12 | * option) any later version. | ||
| 13 | */ | ||
| 14 | #include <linux/clk.h> | ||
| 15 | #include <linux/delay.h> | ||
| 16 | #include <linux/device.h> | ||
| 17 | #include <linux/dma-mapping.h> | ||
| 18 | #include <linux/init.h> | ||
| 19 | #include <linux/interrupt.h> | ||
| 20 | #include <linux/module.h> | ||
| 21 | #include <linux/platform_device.h> | ||
| 22 | #include <linux/slab.h> | ||
| 23 | |||
| 24 | #include <sound/core.h> | ||
| 25 | #include <sound/initval.h> | ||
| 26 | #include <sound/pcm.h> | ||
| 27 | #include <sound/pcm_params.h> | ||
| 28 | #include <sound/soc.h> | ||
| 29 | |||
| 30 | #include <asm/fiq.h> | ||
| 31 | |||
| 32 | #include <mach/ssi.h> | ||
| 33 | |||
| 34 | #include "imx-ssi.h" | ||
| 35 | |||
| 36 | struct imx_pcm_runtime_data { | ||
| 37 | int period; | ||
| 38 | int periods; | ||
| 39 | unsigned long offset; | ||
| 40 | unsigned long last_offset; | ||
| 41 | unsigned long size; | ||
| 42 | struct hrtimer hrt; | ||
| 43 | int poll_time_ns; | ||
| 44 | struct snd_pcm_substream *substream; | ||
| 45 | atomic_t running; | ||
| 46 | }; | ||
| 47 | |||
| 48 | static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt) | ||
| 49 | { | ||
| 50 | struct imx_pcm_runtime_data *iprtd = | ||
| 51 | container_of(hrt, struct imx_pcm_runtime_data, hrt); | ||
| 52 | struct snd_pcm_substream *substream = iprtd->substream; | ||
| 53 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 54 | struct pt_regs regs; | ||
| 55 | unsigned long delta; | ||
| 56 | |||
| 57 | if (!atomic_read(&iprtd->running)) | ||
| 58 | return HRTIMER_NORESTART; | ||
| 59 | |||
| 60 | get_fiq_regs(®s); | ||
| 61 | |||
| 62 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
| 63 | iprtd->offset = regs.ARM_r8 & 0xffff; | ||
| 64 | else | ||
| 65 | iprtd->offset = regs.ARM_r9 & 0xffff; | ||
| 66 | |||
| 67 | /* How much data have we transferred since the last period report? */ | ||
| 68 | if (iprtd->offset >= iprtd->last_offset) | ||
| 69 | delta = iprtd->offset - iprtd->last_offset; | ||
| 70 | else | ||
| 71 | delta = runtime->buffer_size + iprtd->offset | ||
| 72 | - iprtd->last_offset; | ||
| 73 | |||
| 74 | /* If we've transferred at least a period then report it and | ||
| 75 | * reset our poll time */ | ||
| 76 | if (delta >= iprtd->period) { | ||
| 77 | snd_pcm_period_elapsed(substream); | ||
| 78 | iprtd->last_offset = iprtd->offset; | ||
| 79 | } | ||
| 80 | |||
| 81 | hrtimer_forward_now(hrt, ns_to_ktime(iprtd->poll_time_ns)); | ||
| 82 | |||
| 83 | return HRTIMER_RESTART; | ||
| 84 | } | ||
| 85 | |||
| 86 | static struct fiq_handler fh = { | ||
| 87 | .name = DRV_NAME, | ||
| 88 | }; | ||
| 89 | |||
| 90 | static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream, | ||
| 91 | struct snd_pcm_hw_params *params) | ||
| 92 | { | ||
| 93 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 94 | struct imx_pcm_runtime_data *iprtd = runtime->private_data; | ||
| 95 | |||
| 96 | iprtd->size = params_buffer_bytes(params); | ||
| 97 | iprtd->periods = params_periods(params); | ||
| 98 | iprtd->period = params_period_bytes(params) ; | ||
| 99 | iprtd->offset = 0; | ||
| 100 | iprtd->last_offset = 0; | ||
| 101 | iprtd->poll_time_ns = 1000000000 / params_rate(params) * | ||
| 102 | params_period_size(params); | ||
| 103 | snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); | ||
| 104 | |||
| 105 | return 0; | ||
| 106 | } | ||
| 107 | |||
| 108 | static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream) | ||
| 109 | { | ||
| 110 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 111 | struct imx_pcm_runtime_data *iprtd = runtime->private_data; | ||
| 112 | struct pt_regs regs; | ||
| 113 | |||
| 114 | get_fiq_regs(®s); | ||
| 115 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
| 116 | regs.ARM_r8 = (iprtd->period * iprtd->periods - 1) << 16; | ||
| 117 | else | ||
| 118 | regs.ARM_r9 = (iprtd->period * iprtd->periods - 1) << 16; | ||
| 119 | |||
| 120 | set_fiq_regs(®s); | ||
| 121 | |||
| 122 | return 0; | ||
| 123 | } | ||
| 124 | |||
| 125 | static int fiq_enable; | ||
| 126 | static int imx_pcm_fiq; | ||
| 127 | |||
| 128 | static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | ||
| 129 | { | ||
| 130 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 131 | struct imx_pcm_runtime_data *iprtd = runtime->private_data; | ||
| 132 | |||
| 133 | switch (cmd) { | ||
| 134 | case SNDRV_PCM_TRIGGER_START: | ||
| 135 | case SNDRV_PCM_TRIGGER_RESUME: | ||
| 136 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
| 137 | atomic_set(&iprtd->running, 1); | ||
| 138 | hrtimer_start(&iprtd->hrt, ns_to_ktime(iprtd->poll_time_ns), | ||
| 139 | HRTIMER_MODE_REL); | ||
| 140 | if (++fiq_enable == 1) | ||
| 141 | enable_fiq(imx_pcm_fiq); | ||
| 142 | |||
| 143 | break; | ||
| 144 | |||
| 145 | case SNDRV_PCM_TRIGGER_STOP: | ||
| 146 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
| 147 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
| 148 | atomic_set(&iprtd->running, 0); | ||
| 149 | |||
| 150 | if (--fiq_enable == 0) | ||
| 151 | disable_fiq(imx_pcm_fiq); | ||
| 152 | |||
| 153 | break; | ||
| 154 | default: | ||
| 155 | return -EINVAL; | ||
| 156 | } | ||
| 157 | |||
| 158 | return 0; | ||
| 159 | } | ||
| 160 | |||
| 161 | static snd_pcm_uframes_t snd_imx_pcm_pointer(struct snd_pcm_substream *substream) | ||
| 162 | { | ||
| 163 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 164 | struct imx_pcm_runtime_data *iprtd = runtime->private_data; | ||
| 165 | |||
| 166 | return bytes_to_frames(substream->runtime, iprtd->offset); | ||
| 167 | } | ||
| 168 | |||
| 169 | static struct snd_pcm_hardware snd_imx_hardware = { | ||
| 170 | .info = SNDRV_PCM_INFO_INTERLEAVED | | ||
| 171 | SNDRV_PCM_INFO_BLOCK_TRANSFER | | ||
| 172 | SNDRV_PCM_INFO_MMAP | | ||
| 173 | SNDRV_PCM_INFO_MMAP_VALID | | ||
| 174 | SNDRV_PCM_INFO_PAUSE | | ||
| 175 | SNDRV_PCM_INFO_RESUME, | ||
| 176 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
| 177 | .rate_min = 8000, | ||
| 178 | .channels_min = 2, | ||
| 179 | .channels_max = 2, | ||
| 180 | .buffer_bytes_max = IMX_SSI_DMABUF_SIZE, | ||
| 181 | .period_bytes_min = 128, | ||
| 182 | .period_bytes_max = 16 * 1024, | ||
| 183 | .periods_min = 4, | ||
| 184 | .periods_max = 255, | ||
| 185 | .fifo_size = 0, | ||
| 186 | }; | ||
| 187 | |||
| 188 | static int snd_imx_open(struct snd_pcm_substream *substream) | ||
| 189 | { | ||
| 190 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 191 | struct imx_pcm_runtime_data *iprtd; | ||
| 192 | int ret; | ||
| 193 | |||
| 194 | iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL); | ||
| 195 | if (iprtd == NULL) | ||
| 196 | return -ENOMEM; | ||
| 197 | runtime->private_data = iprtd; | ||
| 198 | |||
| 199 | iprtd->substream = substream; | ||
| 200 | |||
| 201 | atomic_set(&iprtd->running, 0); | ||
| 202 | hrtimer_init(&iprtd->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL); | ||
| 203 | iprtd->hrt.function = snd_hrtimer_callback; | ||
| 204 | |||
| 205 | ret = snd_pcm_hw_constraint_integer(substream->runtime, | ||
| 206 | SNDRV_PCM_HW_PARAM_PERIODS); | ||
| 207 | if (ret < 0) { | ||
| 208 | kfree(iprtd); | ||
| 209 | return ret; | ||
| 210 | } | ||
| 211 | |||
| 212 | snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); | ||
| 213 | return 0; | ||
| 214 | } | ||
| 215 | |||
| 216 | static int snd_imx_close(struct snd_pcm_substream *substream) | ||
| 217 | { | ||
| 218 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 219 | struct imx_pcm_runtime_data *iprtd = runtime->private_data; | ||
| 220 | |||
| 221 | hrtimer_cancel(&iprtd->hrt); | ||
| 222 | |||
| 223 | kfree(iprtd); | ||
| 224 | |||
| 225 | return 0; | ||
| 226 | } | ||
| 227 | |||
| 228 | static struct snd_pcm_ops imx_pcm_ops = { | ||
| 229 | .open = snd_imx_open, | ||
| 230 | .close = snd_imx_close, | ||
| 231 | .ioctl = snd_pcm_lib_ioctl, | ||
| 232 | .hw_params = snd_imx_pcm_hw_params, | ||
| 233 | .prepare = snd_imx_pcm_prepare, | ||
| 234 | .trigger = snd_imx_pcm_trigger, | ||
| 235 | .pointer = snd_imx_pcm_pointer, | ||
| 236 | .mmap = snd_imx_pcm_mmap, | ||
| 237 | }; | ||
| 238 | |||
| 239 | static int ssi_irq = 0; | ||
| 240 | |||
| 241 | static int imx_pcm_fiq_new(struct snd_soc_pcm_runtime *rtd) | ||
| 242 | { | ||
| 243 | struct snd_soc_dai *dai = rtd->cpu_dai; | ||
| 244 | struct snd_pcm *pcm = rtd->pcm; | ||
| 245 | int ret; | ||
| 246 | |||
| 247 | ret = imx_pcm_new(rtd); | ||
| 248 | if (ret) | ||
| 249 | return ret; | ||
| 250 | |||
| 251 | if (dai->driver->playback.channels_min) { | ||
| 252 | struct snd_pcm_substream *substream = | ||
| 253 | pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; | ||
| 254 | struct snd_dma_buffer *buf = &substream->dma_buffer; | ||
| 255 | |||
| 256 | imx_ssi_fiq_tx_buffer = (unsigned long)buf->area; | ||
| 257 | } | ||
| 258 | |||
| 259 | if (dai->driver->capture.channels_min) { | ||
| 260 | struct snd_pcm_substream *substream = | ||
| 261 | pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; | ||
| 262 | struct snd_dma_buffer *buf = &substream->dma_buffer; | ||
| 263 | |||
| 264 | imx_ssi_fiq_rx_buffer = (unsigned long)buf->area; | ||
| 265 | } | ||
| 266 | |||
| 267 | set_fiq_handler(&imx_ssi_fiq_start, | ||
| 268 | &imx_ssi_fiq_end - &imx_ssi_fiq_start); | ||
| 269 | |||
| 270 | return 0; | ||
| 271 | } | ||
| 272 | |||
| 273 | static void imx_pcm_fiq_free(struct snd_pcm *pcm) | ||
| 274 | { | ||
| 275 | mxc_set_irq_fiq(ssi_irq, 0); | ||
| 276 | release_fiq(&fh); | ||
| 277 | imx_pcm_free(pcm); | ||
| 278 | } | ||
| 279 | |||
| 280 | static struct snd_soc_platform_driver imx_soc_platform_fiq = { | ||
| 281 | .ops = &imx_pcm_ops, | ||
| 282 | .pcm_new = imx_pcm_fiq_new, | ||
| 283 | .pcm_free = imx_pcm_fiq_free, | ||
| 284 | }; | ||
| 285 | |||
| 286 | static int __devinit imx_soc_platform_probe(struct platform_device *pdev) | ||
| 287 | { | ||
| 288 | struct imx_ssi *ssi = platform_get_drvdata(pdev); | ||
| 289 | int ret; | ||
| 290 | |||
| 291 | ret = claim_fiq(&fh); | ||
| 292 | if (ret) { | ||
| 293 | dev_err(&pdev->dev, "failed to claim fiq: %d", ret); | ||
| 294 | return ret; | ||
| 295 | } | ||
| 296 | |||
| 297 | mxc_set_irq_fiq(ssi->irq, 1); | ||
| 298 | ssi_irq = ssi->irq; | ||
| 299 | |||
| 300 | imx_pcm_fiq = ssi->irq; | ||
| 301 | |||
| 302 | imx_ssi_fiq_base = (unsigned long)ssi->base; | ||
| 303 | |||
| 304 | ssi->dma_params_tx.burstsize = 4; | ||
| 305 | ssi->dma_params_rx.burstsize = 6; | ||
| 306 | |||
| 307 | ret = snd_soc_register_platform(&pdev->dev, &imx_soc_platform_fiq); | ||
| 308 | if (ret) | ||
| 309 | goto failed_register; | ||
| 310 | |||
| 311 | return 0; | ||
| 312 | |||
| 313 | failed_register: | ||
| 314 | mxc_set_irq_fiq(ssi_irq, 0); | ||
| 315 | release_fiq(&fh); | ||
| 316 | |||
| 317 | return ret; | ||
| 318 | } | ||
| 319 | |||
| 320 | static int __devexit imx_soc_platform_remove(struct platform_device *pdev) | ||
| 321 | { | ||
| 322 | snd_soc_unregister_platform(&pdev->dev); | ||
| 323 | return 0; | ||
| 324 | } | ||
| 325 | |||
| 326 | static struct platform_driver imx_pcm_driver = { | ||
| 327 | .driver = { | ||
| 328 | .name = "imx-fiq-pcm-audio", | ||
| 329 | .owner = THIS_MODULE, | ||
| 330 | }, | ||
| 331 | |||
| 332 | .probe = imx_soc_platform_probe, | ||
| 333 | .remove = __devexit_p(imx_soc_platform_remove), | ||
| 334 | }; | ||
| 335 | |||
| 336 | static int __init snd_imx_pcm_init(void) | ||
| 337 | { | ||
| 338 | return platform_driver_register(&imx_pcm_driver); | ||
| 339 | } | ||
| 340 | module_init(snd_imx_pcm_init); | ||
| 341 | |||
| 342 | static void __exit snd_imx_pcm_exit(void) | ||
| 343 | { | ||
| 344 | platform_driver_unregister(&imx_pcm_driver); | ||
| 345 | } | ||
| 346 | module_exit(snd_imx_pcm_exit); | ||
diff --git a/sound/soc/imx/imx-ssi.c b/sound/soc/imx/imx-ssi.c new file mode 100644 index 00000000000..10a8e278375 --- /dev/null +++ b/sound/soc/imx/imx-ssi.c | |||
| @@ -0,0 +1,778 @@ | |||
| 1 | /* | ||
| 2 | * imx-ssi.c -- ALSA Soc Audio Layer | ||
| 3 | * | ||
| 4 | * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> | ||
| 5 | * | ||
| 6 | * This code is based on code copyrighted by Freescale, | ||
| 7 | * Liam Girdwood, Javier Martin and probably others. | ||
| 8 | * | ||
| 9 | * This program is free software; you can redistribute it and/or modify it | ||
| 10 | * under the terms of the GNU General Public License as published by the | ||
| 11 | * Free Software Foundation; either version 2 of the License, or (at your | ||
| 12 | * option) any later version. | ||
| 13 | * | ||
| 14 | * | ||
| 15 | * The i.MX SSI core has some nasty limitations in AC97 mode. While most | ||
| 16 | * sane processor vendors have a FIFO per AC97 slot, the i.MX has only | ||
| 17 | * one FIFO which combines all valid receive slots. We cannot even select | ||
| 18 | * which slots we want to receive. The WM9712 with which this driver | ||
| 19 | * was developed with always sends GPIO status data in slot 12 which | ||
| 20 | * we receive in our (PCM-) data stream. The only chance we have is to | ||
| 21 | * manually skip this data in the FIQ handler. With sampling rates different | ||
| 22 | * from 48000Hz not every frame has valid receive data, so the ratio | ||
| 23 | * between pcm data and GPIO status data changes. Our FIQ handler is not | ||
| 24 | * able to handle this, hence this driver only works with 48000Hz sampling | ||
| 25 | * rate. | ||
| 26 | * Reading and writing AC97 registers is another challenge. The core | ||
| 27 | * provides us status bits when the read register is updated with *another* | ||
| 28 | * value. When we read the same register two times (and the register still | ||
| 29 | * contains the same value) these status bits are not set. We work | ||
| 30 | * around this by not polling these bits but only wait a fixed delay. | ||
| 31 | * | ||
| 32 | */ | ||
| 33 | |||
| 34 | #include <linux/clk.h> | ||
| 35 | #include <linux/delay.h> | ||
| 36 | #include <linux/device.h> | ||
| 37 | #include <linux/dma-mapping.h> | ||
| 38 | #include <linux/init.h> | ||
| 39 | #include <linux/interrupt.h> | ||
| 40 | #include <linux/module.h> | ||
| 41 | #include <linux/platform_device.h> | ||
| 42 | #include <linux/slab.h> | ||
| 43 | |||
| 44 | #include <sound/core.h> | ||
| 45 | #include <sound/initval.h> | ||
| 46 | #include <sound/pcm.h> | ||
| 47 | #include <sound/pcm_params.h> | ||
| 48 | #include <sound/soc.h> | ||
| 49 | |||
| 50 | #include <mach/ssi.h> | ||
| 51 | #include <mach/hardware.h> | ||
| 52 | |||
| 53 | #include "imx-ssi.h" | ||
| 54 | |||
| 55 | #define SSI_SACNT_DEFAULT (SSI_SACNT_AC97EN | SSI_SACNT_FV) | ||
| 56 | |||
| 57 | /* | ||
| 58 | * SSI Network Mode or TDM slots configuration. | ||
| 59 | * Should only be called when port is inactive (i.e. SSIEN = 0). | ||
| 60 | */ | ||
| 61 | static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, | ||
| 62 | unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) | ||
| 63 | { | ||
| 64 | struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); | ||
| 65 | u32 sccr; | ||
| 66 | |||
| 67 | sccr = readl(ssi->base + SSI_STCCR); | ||
| 68 | sccr &= ~SSI_STCCR_DC_MASK; | ||
| 69 | sccr |= SSI_STCCR_DC(slots - 1); | ||
| 70 | writel(sccr, ssi->base + SSI_STCCR); | ||
| 71 | |||
| 72 | sccr = readl(ssi->base + SSI_SRCCR); | ||
| 73 | sccr &= ~SSI_STCCR_DC_MASK; | ||
| 74 | sccr |= SSI_STCCR_DC(slots - 1); | ||
| 75 | writel(sccr, ssi->base + SSI_SRCCR); | ||
| 76 | |||
| 77 | writel(tx_mask, ssi->base + SSI_STMSK); | ||
| 78 | writel(rx_mask, ssi->base + SSI_SRMSK); | ||
| 79 | |||
| 80 | return 0; | ||
| 81 | } | ||
| 82 | |||
| 83 | /* | ||
| 84 | * SSI DAI format configuration. | ||
| 85 | * Should only be called when port is inactive (i.e. SSIEN = 0). | ||
| 86 | */ | ||
| 87 | static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) | ||
| 88 | { | ||
| 89 | struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); | ||
| 90 | u32 strcr = 0, scr; | ||
| 91 | |||
| 92 | scr = readl(ssi->base + SSI_SCR) & ~(SSI_SCR_SYN | SSI_SCR_NET); | ||
| 93 | |||
| 94 | /* DAI mode */ | ||
| 95 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | ||
| 96 | case SND_SOC_DAIFMT_I2S: | ||
| 97 | /* data on rising edge of bclk, frame low 1clk before data */ | ||
| 98 | strcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0; | ||
| 99 | scr |= SSI_SCR_NET; | ||
| 100 | if (ssi->flags & IMX_SSI_USE_I2S_SLAVE) { | ||
| 101 | scr &= ~SSI_I2S_MODE_MASK; | ||
| 102 | scr |= SSI_SCR_I2S_MODE_SLAVE; | ||
| 103 | } | ||
| 104 | break; | ||
| 105 | case SND_SOC_DAIFMT_LEFT_J: | ||
| 106 | /* data on rising edge of bclk, frame high with data */ | ||
| 107 | strcr |= SSI_STCR_TXBIT0; | ||
| 108 | break; | ||
| 109 | case SND_SOC_DAIFMT_DSP_B: | ||
| 110 | /* data on rising edge of bclk, frame high with data */ | ||
| 111 | strcr |= SSI_STCR_TFSL | SSI_STCR_TXBIT0; | ||
| 112 | break; | ||
| 113 | case SND_SOC_DAIFMT_DSP_A: | ||
| 114 | /* data on rising edge of bclk, frame high 1clk before data */ | ||
| 115 | strcr |= SSI_STCR_TFSL | SSI_STCR_TEFS; | ||
| 116 | break; | ||
| 117 | } | ||
| 118 | |||
| 119 | /* DAI clock inversion */ | ||
| 120 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { | ||
| 121 | case SND_SOC_DAIFMT_IB_IF: | ||
| 122 | strcr |= SSI_STCR_TFSI; | ||
| 123 | strcr &= ~SSI_STCR_TSCKP; | ||
| 124 | break; | ||
| 125 | case SND_SOC_DAIFMT_IB_NF: | ||
| 126 | strcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI); | ||
| 127 | break; | ||
| 128 | case SND_SOC_DAIFMT_NB_IF: | ||
| 129 | strcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP; | ||
| 130 | break; | ||
| 131 | case SND_SOC_DAIFMT_NB_NF: | ||
| 132 | strcr &= ~SSI_STCR_TFSI; | ||
| 133 | strcr |= SSI_STCR_TSCKP; | ||
| 134 | break; | ||
| 135 | } | ||
| 136 | |||
| 137 | /* DAI clock master masks */ | ||
| 138 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | ||
| 139 | case SND_SOC_DAIFMT_CBM_CFM: | ||
| 140 | break; | ||
| 141 | default: | ||
| 142 | /* Master mode not implemented, needs handling of clocks. */ | ||
| 143 | return -EINVAL; | ||
| 144 | } | ||
| 145 | |||
| 146 | strcr |= SSI_STCR_TFEN0; | ||
| 147 | |||
| 148 | if (ssi->flags & IMX_SSI_NET) | ||
| 149 | scr |= SSI_SCR_NET; | ||
| 150 | if (ssi->flags & IMX_SSI_SYN) | ||
| 151 | scr |= SSI_SCR_SYN; | ||
| 152 | |||
| 153 | writel(strcr, ssi->base + SSI_STCR); | ||
| 154 | writel(strcr, ssi->base + SSI_SRCR); | ||
| 155 | writel(scr, ssi->base + SSI_SCR); | ||
| 156 | |||
| 157 | return 0; | ||
| 158 | } | ||
| 159 | |||
| 160 | /* | ||
| 161 | * SSI system clock configuration. | ||
| 162 | * Should only be called when port is inactive (i.e. SSIEN = 0). | ||
| 163 | */ | ||
| 164 | static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai, | ||
| 165 | int clk_id, unsigned int freq, int dir) | ||
| 166 | { | ||
| 167 | struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); | ||
| 168 | u32 scr; | ||
| 169 | |||
| 170 | scr = readl(ssi->base + SSI_SCR); | ||
| 171 | |||
| 172 | switch (clk_id) { | ||
| 173 | case IMX_SSP_SYS_CLK: | ||
| 174 | if (dir == SND_SOC_CLOCK_OUT) | ||
| 175 | scr |= SSI_SCR_SYS_CLK_EN; | ||
| 176 | else | ||
| 177 | scr &= ~SSI_SCR_SYS_CLK_EN; | ||
| 178 | break; | ||
| 179 | default: | ||
| 180 | return -EINVAL; | ||
| 181 | } | ||
| 182 | |||
| 183 | writel(scr, ssi->base + SSI_SCR); | ||
| 184 | |||
| 185 | return 0; | ||
| 186 | } | ||
| 187 | |||
| 188 | /* | ||
| 189 | * SSI Clock dividers | ||
| 190 | * Should only be called when port is inactive (i.e. SSIEN = 0). | ||
| 191 | */ | ||
| 192 | static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, | ||
| 193 | int div_id, int div) | ||
| 194 | { | ||
| 195 | struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); | ||
| 196 | u32 stccr, srccr; | ||
| 197 | |||
| 198 | stccr = readl(ssi->base + SSI_STCCR); | ||
| 199 | srccr = readl(ssi->base + SSI_SRCCR); | ||
| 200 | |||
| 201 | switch (div_id) { | ||
| 202 | case IMX_SSI_TX_DIV_2: | ||
| 203 | stccr &= ~SSI_STCCR_DIV2; | ||
| 204 | stccr |= div; | ||
| 205 | break; | ||
| 206 | case IMX_SSI_TX_DIV_PSR: | ||
| 207 | stccr &= ~SSI_STCCR_PSR; | ||
| 208 | stccr |= div; | ||
| 209 | break; | ||
| 210 | case IMX_SSI_TX_DIV_PM: | ||
| 211 | stccr &= ~0xff; | ||
| 212 | stccr |= SSI_STCCR_PM(div); | ||
| 213 | break; | ||
| 214 | case IMX_SSI_RX_DIV_2: | ||
| 215 | stccr &= ~SSI_STCCR_DIV2; | ||
| 216 | stccr |= div; | ||
| 217 | break; | ||
| 218 | case IMX_SSI_RX_DIV_PSR: | ||
| 219 | stccr &= ~SSI_STCCR_PSR; | ||
| 220 | stccr |= div; | ||
| 221 | break; | ||
| 222 | case IMX_SSI_RX_DIV_PM: | ||
| 223 | stccr &= ~0xff; | ||
| 224 | stccr |= SSI_STCCR_PM(div); | ||
| 225 | break; | ||
| 226 | default: | ||
| 227 | return -EINVAL; | ||
| 228 | } | ||
| 229 | |||
| 230 | writel(stccr, ssi->base + SSI_STCCR); | ||
| 231 | writel(srccr, ssi->base + SSI_SRCCR); | ||
| 232 | |||
| 233 | return 0; | ||
| 234 | } | ||
| 235 | |||
| 236 | /* | ||
| 237 | * Should only be called when port is inactive (i.e. SSIEN = 0), | ||
| 238 | * although can be called multiple times by upper layers. | ||
| 239 | */ | ||
| 240 | static int imx_ssi_hw_params(struct snd_pcm_substream *substream, | ||
| 241 | struct snd_pcm_hw_params *params, | ||
| 242 | struct snd_soc_dai *cpu_dai) | ||
| 243 | { | ||
| 244 | struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); | ||
| 245 | struct imx_pcm_dma_params *dma_data; | ||
| 246 | u32 reg, sccr; | ||
| 247 | |||
| 248 | /* Tx/Rx config */ | ||
| 249 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
| 250 | reg = SSI_STCCR; | ||
| 251 | dma_data = &ssi->dma_params_tx; | ||
| 252 | } else { | ||
| 253 | reg = SSI_SRCCR; | ||
| 254 | dma_data = &ssi->dma_params_rx; | ||
| 255 | } | ||
| 256 | |||
| 257 | if (ssi->flags & IMX_SSI_SYN) | ||
| 258 | reg = SSI_STCCR; | ||
| 259 | |||
| 260 | snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); | ||
| 261 | |||
| 262 | sccr = readl(ssi->base + reg) & ~SSI_STCCR_WL_MASK; | ||
| 263 | |||
| 264 | /* DAI data (word) size */ | ||
| 265 | switch (params_format(params)) { | ||
| 266 | case SNDRV_PCM_FORMAT_S16_LE: | ||
| 267 | sccr |= SSI_SRCCR_WL(16); | ||
| 268 | break; | ||
| 269 | case SNDRV_PCM_FORMAT_S20_3LE: | ||
| 270 | sccr |= SSI_SRCCR_WL(20); | ||
| 271 | break; | ||
| 272 | case SNDRV_PCM_FORMAT_S24_LE: | ||
| 273 | sccr |= SSI_SRCCR_WL(24); | ||
| 274 | break; | ||
| 275 | } | ||
| 276 | |||
| 277 | writel(sccr, ssi->base + reg); | ||
| 278 | |||
| 279 | return 0; | ||
| 280 | } | ||
| 281 | |||
| 282 | static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd, | ||
| 283 | struct snd_soc_dai *dai) | ||
| 284 | { | ||
| 285 | struct imx_ssi *ssi = snd_soc_dai_get_drvdata(dai); | ||
| 286 | unsigned int sier_bits, sier; | ||
| 287 | unsigned int scr; | ||
| 288 | |||
| 289 | scr = readl(ssi->base + SSI_SCR); | ||
| 290 | sier = readl(ssi->base + SSI_SIER); | ||
| 291 | |||
| 292 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
| 293 | if (ssi->flags & IMX_SSI_DMA) | ||
| 294 | sier_bits = SSI_SIER_TDMAE; | ||
| 295 | else | ||
| 296 | sier_bits = SSI_SIER_TIE | SSI_SIER_TFE0_EN; | ||
| 297 | } else { | ||
| 298 | if (ssi->flags & IMX_SSI_DMA) | ||
| 299 | sier_bits = SSI_SIER_RDMAE; | ||
| 300 | else | ||
| 301 | sier_bits = SSI_SIER_RIE | SSI_SIER_RFF0_EN; | ||
| 302 | } | ||
| 303 | |||
| 304 | switch (cmd) { | ||
| 305 | case SNDRV_PCM_TRIGGER_START: | ||
| 306 | case SNDRV_PCM_TRIGGER_RESUME: | ||
| 307 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
| 308 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
| 309 | scr |= SSI_SCR_TE; | ||
| 310 | else | ||
| 311 | scr |= SSI_SCR_RE; | ||
| 312 | sier |= sier_bits; | ||
| 313 | |||
| 314 | if (++ssi->enabled == 1) | ||
| 315 | scr |= SSI_SCR_SSIEN; | ||
| 316 | |||
| 317 | break; | ||
| 318 | |||
| 319 | case SNDRV_PCM_TRIGGER_STOP: | ||
| 320 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
| 321 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
| 322 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
| 323 | scr &= ~SSI_SCR_TE; | ||
| 324 | else | ||
| 325 | scr &= ~SSI_SCR_RE; | ||
| 326 | sier &= ~sier_bits; | ||
| 327 | |||
| 328 | if (--ssi->enabled == 0) | ||
| 329 | scr &= ~SSI_SCR_SSIEN; | ||
| 330 | |||
| 331 | break; | ||
| 332 | default: | ||
| 333 | return -EINVAL; | ||
| 334 | } | ||
| 335 | |||
| 336 | if (!(ssi->flags & IMX_SSI_USE_AC97)) | ||
| 337 | /* rx/tx are always enabled to access ac97 registers */ | ||
| 338 | writel(scr, ssi->base + SSI_SCR); | ||
| 339 | |||
| 340 | writel(sier, ssi->base + SSI_SIER); | ||
| 341 | |||
| 342 | return 0; | ||
| 343 | } | ||
| 344 | |||
| 345 | static struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = { | ||
| 346 | .hw_params = imx_ssi_hw_params, | ||
| 347 | .set_fmt = imx_ssi_set_dai_fmt, | ||
| 348 | .set_clkdiv = imx_ssi_set_dai_clkdiv, | ||
| 349 | .set_sysclk = imx_ssi_set_dai_sysclk, | ||
| 350 | .set_tdm_slot = imx_ssi_set_dai_tdm_slot, | ||
| 351 | .trigger = imx_ssi_trigger, | ||
| 352 | }; | ||
| 353 | |||
| 354 | int snd_imx_pcm_mmap(struct snd_pcm_substream *substream, | ||
| 355 | struct vm_area_struct *vma) | ||
| 356 | { | ||
| 357 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
| 358 | int ret; | ||
| 359 | |||
| 360 | ret = dma_mmap_coherent(NULL, vma, runtime->dma_area, | ||
| 361 | runtime->dma_addr, runtime->dma_bytes); | ||
| 362 | |||
| 363 | pr_debug("%s: ret: %d %p 0x%08x 0x%08x\n", __func__, ret, | ||
| 364 | runtime->dma_area, | ||
| 365 | runtime->dma_addr, | ||
| 366 | runtime->dma_bytes); | ||
| 367 | return ret; | ||
| 368 | } | ||
| 369 | EXPORT_SYMBOL_GPL(snd_imx_pcm_mmap); | ||
| 370 | |||
| 371 | static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) | ||
| 372 | { | ||
| 373 | struct snd_pcm_substream *substream = pcm->streams[stream].substream; | ||
| 374 | struct snd_dma_buffer *buf = &substream->dma_buffer; | ||
| 375 | size_t size = IMX_SSI_DMABUF_SIZE; | ||
| 376 | |||
| 377 | buf->dev.type = SNDRV_DMA_TYPE_DEV; | ||
| 378 | buf->dev.dev = pcm->card->dev; | ||
| 379 | buf->private_data = NULL; | ||
| 380 | buf->area = dma_alloc_writecombine(pcm->card->dev, size, | ||
| 381 | &buf->addr, GFP_KERNEL); | ||
| 382 | if (!buf->area) | ||
| 383 | return -ENOMEM; | ||
| 384 | buf->bytes = size; | ||
| 385 | |||
| 386 | return 0; | ||
| 387 | } | ||
| 388 | |||
| 389 | static u64 imx_pcm_dmamask = DMA_BIT_MASK(32); | ||
| 390 | |||
| 391 | int imx_pcm_new(struct snd_soc_pcm_runtime *rtd) | ||
| 392 | { | ||
| 393 | struct snd_card *card = rtd->card->snd_card; | ||
| 394 | struct snd_soc_dai *dai = rtd->cpu_dai; | ||
| 395 | struct snd_pcm *pcm = rtd->pcm; | ||
| 396 | int ret = 0; | ||
| 397 | |||
| 398 | if (!card->dev->dma_mask) | ||
| 399 | card->dev->dma_mask = &imx_pcm_dmamask; | ||
| 400 | if (!card->dev->coherent_dma_mask) | ||
| 401 | card->dev->coherent_dma_mask = DMA_BIT_MASK(32); | ||
| 402 | if (dai->driver->playback.channels_min) { | ||
| 403 | ret = imx_pcm_preallocate_dma_buffer(pcm, | ||
| 404 | SNDRV_PCM_STREAM_PLAYBACK); | ||
| 405 | if (ret) | ||
| 406 | goto out; | ||
| 407 | } | ||
| 408 | |||
| 409 | if (dai->driver->capture.channels_min) { | ||
| 410 | ret = imx_pcm_preallocate_dma_buffer(pcm, | ||
| 411 | SNDRV_PCM_STREAM_CAPTURE); | ||
| 412 | if (ret) | ||
| 413 | goto out; | ||
| 414 | } | ||
| 415 | |||
| 416 | out: | ||
| 417 | return ret; | ||
| 418 | } | ||
| 419 | EXPORT_SYMBOL_GPL(imx_pcm_new); | ||
| 420 | |||
| 421 | void imx_pcm_free(struct snd_pcm *pcm) | ||
| 422 | { | ||
| 423 | struct snd_pcm_substream *substream; | ||
| 424 | struct snd_dma_buffer *buf; | ||
| 425 | int stream; | ||
| 426 | |||
| 427 | for (stream = 0; stream < 2; stream++) { | ||
| 428 | substream = pcm->streams[stream].substream; | ||
| 429 | if (!substream) | ||
| 430 | continue; | ||
| 431 | |||
| 432 | buf = &substream->dma_buffer; | ||
| 433 | if (!buf->area) | ||
| 434 | continue; | ||
| 435 | |||
| 436 | dma_free_writecombine(pcm->card->dev, buf->bytes, | ||
| 437 | buf->area, buf->addr); | ||
| 438 | buf->area = NULL; | ||
| 439 | } | ||
| 440 | } | ||
| 441 | EXPORT_SYMBOL_GPL(imx_pcm_free); | ||
| 442 | |||
| 443 | static int imx_ssi_dai_probe(struct snd_soc_dai *dai) | ||
| 444 | { | ||
| 445 | struct imx_ssi *ssi = dev_get_drvdata(dai->dev); | ||
| 446 | uint32_t val; | ||
| 447 | |||
| 448 | snd_soc_dai_set_drvdata(dai, ssi); | ||
| 449 | |||
| 450 | val = SSI_SFCSR_TFWM0(ssi->dma_params_tx.burstsize) | | ||
| 451 | SSI_SFCSR_RFWM0(ssi->dma_params_rx.burstsize); | ||
| 452 | writel(val, ssi->base + SSI_SFCSR); | ||
| 453 | |||
| 454 | return 0; | ||
| 455 | } | ||
| 456 | |||
| 457 | static struct snd_soc_dai_driver imx_ssi_dai = { | ||
| 458 | .probe = imx_ssi_dai_probe, | ||
| 459 | .playback = { | ||
| 460 | .channels_min = 1, | ||
| 461 | .channels_max = 2, | ||
| 462 | .rates = SNDRV_PCM_RATE_8000_96000, | ||
| 463 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
| 464 | }, | ||
| 465 | .capture = { | ||
| 466 | .channels_min = 1, | ||
| 467 | .channels_max = 2, | ||
| 468 | .rates = SNDRV_PCM_RATE_8000_96000, | ||
| 469 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
| 470 | }, | ||
| 471 | .ops = &imx_ssi_pcm_dai_ops, | ||
| 472 | }; | ||
| 473 | |||
| 474 | static struct snd_soc_dai_driver imx_ac97_dai = { | ||
| 475 | .probe = imx_ssi_dai_probe, | ||
| 476 | .ac97_control = 1, | ||
| 477 | .playback = { | ||
| 478 | .stream_name = "AC97 Playback", | ||
| 479 | .channels_min = 2, | ||
| 480 | .channels_max = 2, | ||
| 481 | .rates = SNDRV_PCM_RATE_48000, | ||
| 482 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
| 483 | }, | ||
| 484 | .capture = { | ||
| 485 | .stream_name = "AC97 Capture", | ||
| 486 | .channels_min = 2, | ||
| 487 | .channels_max = 2, | ||
| 488 | .rates = SNDRV_PCM_RATE_48000, | ||
| 489 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
| 490 | }, | ||
| 491 | .ops = &imx_ssi_pcm_dai_ops, | ||
| 492 | }; | ||
| 493 | |||
| 494 | static void setup_channel_to_ac97(struct imx_ssi *imx_ssi) | ||
| 495 | { | ||
| 496 | void __iomem *base = imx_ssi->base; | ||
| 497 | |||
| 498 | writel(0x0, base + SSI_SCR); | ||
| 499 | writel(0x0, base + SSI_STCR); | ||
| 500 | writel(0x0, base + SSI_SRCR); | ||
| 501 | |||
| 502 | writel(SSI_SCR_SYN | SSI_SCR_NET, base + SSI_SCR); | ||
| 503 | |||
| 504 | writel(SSI_SFCSR_RFWM0(8) | | ||
| 505 | SSI_SFCSR_TFWM0(8) | | ||
| 506 | SSI_SFCSR_RFWM1(8) | | ||
| 507 | SSI_SFCSR_TFWM1(8), base + SSI_SFCSR); | ||
| 508 | |||
| 509 | writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_STCCR); | ||
| 510 | writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_SRCCR); | ||
| 511 | |||
| 512 | writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN, base + SSI_SCR); | ||
| 513 | writel(SSI_SOR_WAIT(3), base + SSI_SOR); | ||
| 514 | |||
| 515 | writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN | | ||
| 516 | SSI_SCR_TE | SSI_SCR_RE, | ||
| 517 | base + SSI_SCR); | ||
| 518 | |||
| 519 | writel(SSI_SACNT_DEFAULT, base + SSI_SACNT); | ||
| 520 | writel(0xff, base + SSI_SACCDIS); | ||
| 521 | writel(0x300, base + SSI_SACCEN); | ||
| 522 | } | ||
| 523 | |||
| 524 | static struct imx_ssi *ac97_ssi; | ||
| 525 | |||
| 526 | static void imx_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, | ||
| 527 | unsigned short val) | ||
| 528 | { | ||
| 529 | struct imx_ssi *imx_ssi = ac97_ssi; | ||
| 530 | void __iomem *base = imx_ssi->base; | ||
| 531 | unsigned int lreg; | ||
| 532 | unsigned int lval; | ||
| 533 | |||
| 534 | if (reg > 0x7f) | ||
| 535 | return; | ||
| 536 | |||
| 537 | pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val); | ||
| 538 | |||
| 539 | lreg = reg << 12; | ||
| 540 | writel(lreg, base + SSI_SACADD); | ||
| 541 | |||
| 542 | lval = val << 4; | ||
| 543 | writel(lval , base + SSI_SACDAT); | ||
| 544 | |||
| 545 | writel(SSI_SACNT_DEFAULT | SSI_SACNT_WR, base + SSI_SACNT); | ||
| 546 | udelay(100); | ||
| 547 | } | ||
| 548 | |||
| 549 | static unsigned short imx_ssi_ac97_read(struct snd_ac97 *ac97, | ||
| 550 | unsigned short reg) | ||
| 551 | { | ||
| 552 | struct imx_ssi *imx_ssi = ac97_ssi; | ||
| 553 | void __iomem *base = imx_ssi->base; | ||
| 554 | |||
| 555 | unsigned short val = -1; | ||
| 556 | unsigned int lreg; | ||
| 557 | |||
| 558 | lreg = (reg & 0x7f) << 12 ; | ||
| 559 | writel(lreg, base + SSI_SACADD); | ||
| 560 | writel(SSI_SACNT_DEFAULT | SSI_SACNT_RD, base + SSI_SACNT); | ||
| 561 | |||
| 562 | udelay(100); | ||
| 563 | |||
| 564 | val = (readl(base + SSI_SACDAT) >> 4) & 0xffff; | ||
| 565 | |||
| 566 | pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val); | ||
| 567 | |||
| 568 | return val; | ||
| 569 | } | ||
| 570 | |||
| 571 | static void imx_ssi_ac97_reset(struct snd_ac97 *ac97) | ||
| 572 | { | ||
| 573 | struct imx_ssi *imx_ssi = ac97_ssi; | ||
| 574 | |||
| 575 | if (imx_ssi->ac97_reset) | ||
| 576 | imx_ssi->ac97_reset(ac97); | ||
| 577 | } | ||
| 578 | |||
| 579 | static void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97) | ||
| 580 | { | ||
| 581 | struct imx_ssi *imx_ssi = ac97_ssi; | ||
| 582 | |||
| 583 | if (imx_ssi->ac97_warm_reset) | ||
| 584 | imx_ssi->ac97_warm_reset(ac97); | ||
| 585 | } | ||
| 586 | |||
| 587 | struct snd_ac97_bus_ops soc_ac97_ops = { | ||
| 588 | .read = imx_ssi_ac97_read, | ||
| 589 | .write = imx_ssi_ac97_write, | ||
| 590 | .reset = imx_ssi_ac97_reset, | ||
| 591 | .warm_reset = imx_ssi_ac97_warm_reset | ||
| 592 | }; | ||
| 593 | EXPORT_SYMBOL_GPL(soc_ac97_ops); | ||
| 594 | |||
| 595 | static int imx_ssi_probe(struct platform_device *pdev) | ||
| 596 | { | ||
| 597 | struct resource *res; | ||
| 598 | struct imx_ssi *ssi; | ||
| 599 | struct imx_ssi_platform_data *pdata = pdev->dev.platform_data; | ||
| 600 | int ret = 0; | ||
| 601 | struct snd_soc_dai_driver *dai; | ||
| 602 | |||
| 603 | ssi = kzalloc(sizeof(*ssi), GFP_KERNEL); | ||
| 604 | if (!ssi) | ||
| 605 | return -ENOMEM; | ||
| 606 | dev_set_drvdata(&pdev->dev, ssi); | ||
| 607 | |||
| 608 | if (pdata) { | ||
| 609 | ssi->ac97_reset = pdata->ac97_reset; | ||
| 610 | ssi->ac97_warm_reset = pdata->ac97_warm_reset; | ||
| 611 | ssi->flags = pdata->flags; | ||
| 612 | } | ||
| 613 | |||
| 614 | ssi->irq = platform_get_irq(pdev, 0); | ||
| 615 | |||
| 616 | ssi->clk = clk_get(&pdev->dev, NULL); | ||
| 617 | if (IS_ERR(ssi->clk)) { | ||
| 618 | ret = PTR_ERR(ssi->clk); | ||
| 619 | dev_err(&pdev->dev, "Cannot get the clock: %d\n", | ||
| 620 | ret); | ||
| 621 | goto failed_clk; | ||
| 622 | } | ||
| 623 | clk_enable(ssi->clk); | ||
| 624 | |||
| 625 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
| 626 | if (!res) { | ||
| 627 | ret = -ENODEV; | ||
| 628 | goto failed_get_resource; | ||
| 629 | } | ||
| 630 | |||
| 631 | if (!request_mem_region(res->start, resource_size(res), DRV_NAME)) { | ||
| 632 | dev_err(&pdev->dev, "request_mem_region failed\n"); | ||
| 633 | ret = -EBUSY; | ||
| 634 | goto failed_get_resource; | ||
| 635 | } | ||
| 636 | |||
| 637 | ssi->base = ioremap(res->start, resource_size(res)); | ||
| 638 | if (!ssi->base) { | ||
| 639 | dev_err(&pdev->dev, "ioremap failed\n"); | ||
| 640 | ret = -ENODEV; | ||
| 641 | goto failed_ioremap; | ||
| 642 | } | ||
| 643 | |||
| 644 | if (ssi->flags & IMX_SSI_USE_AC97) { | ||
| 645 | if (ac97_ssi) { | ||
| 646 | ret = -EBUSY; | ||
| 647 | goto failed_ac97; | ||
| 648 | } | ||
| 649 | ac97_ssi = ssi; | ||
| 650 | setup_channel_to_ac97(ssi); | ||
| 651 | dai = &imx_ac97_dai; | ||
| 652 | } else | ||
| 653 | dai = &imx_ssi_dai; | ||
| 654 | |||
| 655 | writel(0x0, ssi->base + SSI_SIER); | ||
| 656 | |||
| 657 | ssi->dma_params_rx.dma_addr = res->start + SSI_SRX0; | ||
| 658 | ssi->dma_params_tx.dma_addr = res->start + SSI_STX0; | ||
| 659 | |||
| 660 | ssi->dma_params_tx.burstsize = 4; | ||
| 661 | ssi->dma_params_rx.burstsize = 4; | ||
| 662 | |||
| 663 | res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0"); | ||
| 664 | if (res) | ||
| 665 | ssi->dma_params_tx.dma = res->start; | ||
| 666 | |||
| 667 | res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0"); | ||
| 668 | if (res) | ||
| 669 | ssi->dma_params_rx.dma = res->start; | ||
| 670 | |||
| 671 | platform_set_drvdata(pdev, ssi); | ||
| 672 | |||
| 673 | ret = snd_soc_register_dai(&pdev->dev, dai); | ||
| 674 | if (ret) { | ||
| 675 | dev_err(&pdev->dev, "register DAI failed\n"); | ||
| 676 | goto failed_register; | ||
| 677 | } | ||
| 678 | |||
| 679 | ssi->soc_platform_pdev_fiq = platform_device_alloc("imx-fiq-pcm-audio", pdev->id); | ||
| 680 | if (!ssi->soc_platform_pdev_fiq) { | ||
| 681 | ret = -ENOMEM; | ||
| 682 | goto failed_pdev_fiq_alloc; | ||
| 683 | } | ||
| 684 | |||
| 685 | platform_set_drvdata(ssi->soc_platform_pdev_fiq, ssi); | ||
| 686 | ret = platform_device_add(ssi->soc_platform_pdev_fiq); | ||
| 687 | if (ret) { | ||
| 688 | dev_err(&pdev->dev, "failed to add platform device\n"); | ||
| 689 | goto failed_pdev_fiq_add; | ||
| 690 | } | ||
| 691 | |||
| 692 | ssi->soc_platform_pdev = platform_device_alloc("imx-pcm-audio", pdev->id); | ||
| 693 | if (!ssi->soc_platform_pdev) { | ||
| 694 | ret = -ENOMEM; | ||
| 695 | goto failed_pdev_alloc; | ||
| 696 | } | ||
| 697 | |||
| 698 | platform_set_drvdata(ssi->soc_platform_pdev, ssi); | ||
| 699 | ret = platform_device_add(ssi->soc_platform_pdev); | ||
| 700 | if (ret) { | ||
| 701 | dev_err(&pdev->dev, "failed to add platform device\n"); | ||
| 702 | goto failed_pdev_add; | ||
| 703 | } | ||
| 704 | |||
| 705 | return 0; | ||
| 706 | |||
| 707 | failed_pdev_add: | ||
| 708 | platform_device_put(ssi->soc_platform_pdev); | ||
| 709 | failed_pdev_alloc: | ||
| 710 | platform_device_del(ssi->soc_platform_pdev_fiq); | ||
| 711 | failed_pdev_fiq_add: | ||
| 712 | platform_device_put(ssi->soc_platform_pdev_fiq); | ||
| 713 | failed_pdev_fiq_alloc: | ||
| 714 | snd_soc_unregister_dai(&pdev->dev); | ||
| 715 | failed_register: | ||
| 716 | failed_ac97: | ||
| 717 | iounmap(ssi->base); | ||
| 718 | failed_ioremap: | ||
| 719 | release_mem_region(res->start, resource_size(res)); | ||
| 720 | failed_get_resource: | ||
| 721 | clk_disable(ssi->clk); | ||
| 722 | clk_put(ssi->clk); | ||
| 723 | failed_clk: | ||
| 724 | kfree(ssi); | ||
| 725 | |||
| 726 | return ret; | ||
| 727 | } | ||
| 728 | |||
| 729 | static int __devexit imx_ssi_remove(struct platform_device *pdev) | ||
| 730 | { | ||
| 731 | struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
| 732 | struct imx_ssi *ssi = platform_get_drvdata(pdev); | ||
| 733 | |||
| 734 | platform_device_unregister(ssi->soc_platform_pdev); | ||
| 735 | platform_device_unregister(ssi->soc_platform_pdev_fiq); | ||
| 736 | |||
| 737 | snd_soc_unregister_dai(&pdev->dev); | ||
| 738 | |||
| 739 | if (ssi->flags & IMX_SSI_USE_AC97) | ||
| 740 | ac97_ssi = NULL; | ||
| 741 | |||
| 742 | iounmap(ssi->base); | ||
| 743 | release_mem_region(res->start, resource_size(res)); | ||
| 744 | clk_disable(ssi->clk); | ||
| 745 | clk_put(ssi->clk); | ||
| 746 | kfree(ssi); | ||
| 747 | |||
| 748 | return 0; | ||
| 749 | } | ||
| 750 | |||
| 751 | static struct platform_driver imx_ssi_driver = { | ||
| 752 | .probe = imx_ssi_probe, | ||
| 753 | .remove = __devexit_p(imx_ssi_remove), | ||
| 754 | |||
| 755 | .driver = { | ||
| 756 | .name = "imx-ssi", | ||
| 757 | .owner = THIS_MODULE, | ||
| 758 | }, | ||
| 759 | }; | ||
| 760 | |||
| 761 | static int __init imx_ssi_init(void) | ||
| 762 | { | ||
| 763 | return platform_driver_register(&imx_ssi_driver); | ||
| 764 | } | ||
| 765 | |||
| 766 | static void __exit imx_ssi_exit(void) | ||
| 767 | { | ||
| 768 | platform_driver_unregister(&imx_ssi_driver); | ||
| 769 | } | ||
| 770 | |||
| 771 | module_init(imx_ssi_init); | ||
| 772 | module_exit(imx_ssi_exit); | ||
| 773 | |||
| 774 | /* Module information */ | ||
| 775 | MODULE_AUTHOR("Sascha Hauer, <s.hauer@pengutronix.de>"); | ||
| 776 | MODULE_DESCRIPTION("i.MX I2S/ac97 SoC Interface"); | ||
| 777 | MODULE_LICENSE("GPL"); | ||
| 778 | MODULE_ALIAS("platform:imx-ssi"); | ||
diff --git a/sound/soc/imx/imx-ssi.h b/sound/soc/imx/imx-ssi.h new file mode 100644 index 00000000000..0a84cec3599 --- /dev/null +++ b/sound/soc/imx/imx-ssi.h | |||
| @@ -0,0 +1,236 @@ | |||
| 1 | /* | ||
| 2 | * This program is free software; you can redistribute it and/or modify | ||
| 3 | * it under the terms of the GNU General Public License version 2 as | ||
| 4 | * published by the Free Software Foundation. | ||
| 5 | */ | ||
| 6 | |||
| 7 | #ifndef _IMX_SSI_H | ||
| 8 | #define _IMX_SSI_H | ||
| 9 | |||
| 10 | #define SSI_STX0 0x00 | ||
| 11 | #define SSI_STX1 0x04 | ||
| 12 | #define SSI_SRX0 0x08 | ||
| 13 | #define SSI_SRX1 0x0c | ||
| 14 | |||
| 15 | #define SSI_SCR 0x10 | ||
| 16 | #define SSI_SCR_CLK_IST (1 << 9) | ||
| 17 | #define SSI_SCR_CLK_IST_SHIFT 9 | ||
| 18 | #define SSI_SCR_TCH_EN (1 << 8) | ||
| 19 | #define SSI_SCR_SYS_CLK_EN (1 << 7) | ||
| 20 | #define SSI_SCR_I2S_MODE_NORM (0 << 5) | ||
| 21 | #define SSI_SCR_I2S_MODE_MSTR (1 << 5) | ||
| 22 | #define SSI_SCR_I2S_MODE_SLAVE (2 << 5) | ||
| 23 | #define SSI_I2S_MODE_MASK (3 << 5) | ||
| 24 | #define SSI_SCR_SYN (1 << 4) | ||
| 25 | #define SSI_SCR_NET (1 << 3) | ||
| 26 | #define SSI_SCR_RE (1 << 2) | ||
| 27 | #define SSI_SCR_TE (1 << 1) | ||
| 28 | #define SSI_SCR_SSIEN (1 << 0) | ||
| 29 | |||
| 30 | #define SSI_SISR 0x14 | ||
| 31 | #define SSI_SISR_MASK ((1 << 19) - 1) | ||
| 32 | #define SSI_SISR_CMDAU (1 << 18) | ||
| 33 | #define SSI_SISR_CMDDU (1 << 17) | ||
| 34 | #define SSI_SISR_RXT (1 << 16) | ||
| 35 | #define SSI_SISR_RDR1 (1 << 15) | ||
| 36 | #define SSI_SISR_RDR0 (1 << 14) | ||
| 37 | #define SSI_SISR_TDE1 (1 << 13) | ||
| 38 | #define SSI_SISR_TDE0 (1 << 12) | ||
| 39 | #define SSI_SISR_ROE1 (1 << 11) | ||
| 40 | #define SSI_SISR_ROE0 (1 << 10) | ||
| 41 | #define SSI_SISR_TUE1 (1 << 9) | ||
| 42 | #define SSI_SISR_TUE0 (1 << 8) | ||
| 43 | #define SSI_SISR_TFS (1 << 7) | ||
| 44 | #define SSI_SISR_RFS (1 << 6) | ||
| 45 | #define SSI_SISR_TLS (1 << 5) | ||
| 46 | #define SSI_SISR_RLS (1 << 4) | ||
| 47 | #define SSI_SISR_RFF1 (1 << 3) | ||
| 48 | #define SSI_SISR_RFF0 (1 << 2) | ||
| 49 | #define SSI_SISR_TFE1 (1 << 1) | ||
| 50 | #define SSI_SISR_TFE0 (1 << 0) | ||
| 51 | |||
| 52 | #define SSI_SIER 0x18 | ||
| 53 | #define SSI_SIER_RDMAE (1 << 22) | ||
| 54 | #define SSI_SIER_RIE (1 << 21) | ||
| 55 | #define SSI_SIER_TDMAE (1 << 20) | ||
| 56 | #define SSI_SIER_TIE (1 << 19) | ||
| 57 | #define SSI_SIER_CMDAU_EN (1 << 18) | ||
| 58 | #define SSI_SIER_CMDDU_EN (1 << 17) | ||
| 59 | #define SSI_SIER_RXT_EN (1 << 16) | ||
| 60 | #define SSI_SIER_RDR1_EN (1 << 15) | ||
| 61 | #define SSI_SIER_RDR0_EN (1 << 14) | ||
| 62 | #define SSI_SIER_TDE1_EN (1 << 13) | ||
| 63 | #define SSI_SIER_TDE0_EN (1 << 12) | ||
| 64 | #define SSI_SIER_ROE1_EN (1 << 11) | ||
| 65 | #define SSI_SIER_ROE0_EN (1 << 10) | ||
| 66 | #define SSI_SIER_TUE1_EN (1 << 9) | ||
| 67 | #define SSI_SIER_TUE0_EN (1 << 8) | ||
| 68 | #define SSI_SIER_TFS_EN (1 << 7) | ||
| 69 | #define SSI_SIER_RFS_EN (1 << 6) | ||
| 70 | #define SSI_SIER_TLS_EN (1 << 5) | ||
| 71 | #define SSI_SIER_RLS_EN (1 << 4) | ||
| 72 | #define SSI_SIER_RFF1_EN (1 << 3) | ||
| 73 | #define SSI_SIER_RFF0_EN (1 << 2) | ||
| 74 | #define SSI_SIER_TFE1_EN (1 << 1) | ||
| 75 | #define SSI_SIER_TFE0_EN (1 << 0) | ||
| 76 | |||
| 77 | #define SSI_STCR 0x1c | ||
| 78 | #define SSI_STCR_TXBIT0 (1 << 9) | ||
| 79 | #define SSI_STCR_TFEN1 (1 << 8) | ||
| 80 | #define SSI_STCR_TFEN0 (1 << 7) | ||
| 81 | #define SSI_FIFO_ENABLE_0_SHIFT 7 | ||
| 82 | #define SSI_STCR_TFDIR (1 << 6) | ||
| 83 | #define SSI_STCR_TXDIR (1 << 5) | ||
| 84 | #define SSI_STCR_TSHFD (1 << 4) | ||
| 85 | #define SSI_STCR_TSCKP (1 << 3) | ||
| 86 | #define SSI_STCR_TFSI (1 << 2) | ||
| 87 | #define SSI_STCR_TFSL (1 << 1) | ||
| 88 | #define SSI_STCR_TEFS (1 << 0) | ||
| 89 | |||
| 90 | #define SSI_SRCR 0x20 | ||
| 91 | #define SSI_SRCR_RXBIT0 (1 << 9) | ||
| 92 | #define SSI_SRCR_RFEN1 (1 << 8) | ||
| 93 | #define SSI_SRCR_RFEN0 (1 << 7) | ||
| 94 | #define SSI_FIFO_ENABLE_0_SHIFT 7 | ||
| 95 | #define SSI_SRCR_RFDIR (1 << 6) | ||
| 96 | #define SSI_SRCR_RXDIR (1 << 5) | ||
| 97 | #define SSI_SRCR_RSHFD (1 << 4) | ||
| 98 | #define SSI_SRCR_RSCKP (1 << 3) | ||
| 99 | #define SSI_SRCR_RFSI (1 << 2) | ||
| 100 | #define SSI_SRCR_RFSL (1 << 1) | ||
| 101 | #define SSI_SRCR_REFS (1 << 0) | ||
| 102 | |||
| 103 | #define SSI_SRCCR 0x28 | ||
| 104 | #define SSI_SRCCR_DIV2 (1 << 18) | ||
| 105 | #define SSI_SRCCR_PSR (1 << 17) | ||
| 106 | #define SSI_SRCCR_WL(x) ((((x) - 2) >> 1) << 13) | ||
| 107 | #define SSI_SRCCR_DC(x) (((x) & 0x1f) << 8) | ||
| 108 | #define SSI_SRCCR_PM(x) (((x) & 0xff) << 0) | ||
| 109 | #define SSI_SRCCR_WL_MASK (0xf << 13) | ||
| 110 | #define SSI_SRCCR_DC_MASK (0x1f << 8) | ||
| 111 | #define SSI_SRCCR_PM_MASK (0xff << 0) | ||
| 112 | |||
| 113 | #define SSI_STCCR 0x24 | ||
| 114 | #define SSI_STCCR_DIV2 (1 << 18) | ||
| 115 | #define SSI_STCCR_PSR (1 << 17) | ||
| 116 | #define SSI_STCCR_WL(x) ((((x) - 2) >> 1) << 13) | ||
| 117 | #define SSI_STCCR_DC(x) (((x) & 0x1f) << 8) | ||
| 118 | #define SSI_STCCR_PM(x) (((x) & 0xff) << 0) | ||
| 119 | #define SSI_STCCR_WL_MASK (0xf << 13) | ||
| 120 | #define SSI_STCCR_DC_MASK (0x1f << 8) | ||
| 121 | #define SSI_STCCR_PM_MASK (0xff << 0) | ||
| 122 | |||
| 123 | #define SSI_SFCSR 0x2c | ||
| 124 | #define SSI_SFCSR_RFCNT1(x) (((x) & 0xf) << 28) | ||
| 125 | #define SSI_RX_FIFO_1_COUNT_SHIFT 28 | ||
| 126 | #define SSI_SFCSR_TFCNT1(x) (((x) & 0xf) << 24) | ||
| 127 | #define SSI_TX_FIFO_1_COUNT_SHIFT 24 | ||
| 128 | #define SSI_SFCSR_RFWM1(x) (((x) & 0xf) << 20) | ||
| 129 | #define SSI_SFCSR_TFWM1(x) (((x) & 0xf) << 16) | ||
| 130 | #define SSI_SFCSR_RFCNT0(x) (((x) & 0xf) << 12) | ||
| 131 | #define SSI_RX_FIFO_0_COUNT_SHIFT 12 | ||
| 132 | #define SSI_SFCSR_TFCNT0(x) (((x) & 0xf) << 8) | ||
| 133 | #define SSI_TX_FIFO_0_COUNT_SHIFT 8 | ||
| 134 | #define SSI_SFCSR_RFWM0(x) (((x) & 0xf) << 4) | ||
| 135 | #define SSI_SFCSR_TFWM0(x) (((x) & 0xf) << 0) | ||
| 136 | #define SSI_SFCSR_RFWM0_MASK (0xf << 4) | ||
| 137 | #define SSI_SFCSR_TFWM0_MASK (0xf << 0) | ||
| 138 | |||
| 139 | #define SSI_STR 0x30 | ||
| 140 | #define SSI_STR_TEST (1 << 15) | ||
| 141 | #define SSI_STR_RCK2TCK (1 << 14) | ||
| 142 | #define SSI_STR_RFS2TFS (1 << 13) | ||
| 143 | #define SSI_STR_RXSTATE(x) (((x) & 0xf) << 8) | ||
| 144 | #define SSI_STR_TXD2RXD (1 << 7) | ||
| 145 | #define SSI_STR_TCK2RCK (1 << 6) | ||
| 146 | #define SSI_STR_TFS2RFS (1 << 5) | ||
| 147 | #define SSI_STR_TXSTATE(x) (((x) & 0xf) << 0) | ||
| 148 | |||
| 149 | #define SSI_SOR 0x34 | ||
| 150 | #define SSI_SOR_CLKOFF (1 << 6) | ||
| 151 | #define SSI_SOR_RX_CLR (1 << 5) | ||
| 152 | #define SSI_SOR_TX_CLR (1 << 4) | ||
| 153 | #define SSI_SOR_INIT (1 << 3) | ||
| 154 | #define SSI_SOR_WAIT(x) (((x) & 0x3) << 1) | ||
| 155 | #define SSI_SOR_WAIT_MASK (0x3 << 1) | ||
| 156 | #define SSI_SOR_SYNRST (1 << 0) | ||
| 157 | |||
| 158 | #define SSI_SACNT 0x38 | ||
| 159 | #define SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5) | ||
| 160 | #define SSI_SACNT_WR (1 << 4) | ||
| 161 | #define SSI_SACNT_RD (1 << 3) | ||
| 162 | #define SSI_SACNT_TIF (1 << 2) | ||
| 163 | #define SSI_SACNT_FV (1 << 1) | ||
| 164 | #define SSI_SACNT_AC97EN (1 << 0) | ||
| 165 | |||
| 166 | #define SSI_SACADD 0x3c | ||
| 167 | #define SSI_SACDAT 0x40 | ||
| 168 | #define SSI_SATAG 0x44 | ||
| 169 | #define SSI_STMSK 0x48 | ||
| 170 | #define SSI_SRMSK 0x4c | ||
| 171 | #define SSI_SACCST 0x50 | ||
| 172 | #define SSI_SACCEN 0x54 | ||
| 173 | #define SSI_SACCDIS 0x58 | ||
| 174 | |||
| 175 | /* SSI clock sources */ | ||
| 176 | #define IMX_SSP_SYS_CLK 0 | ||
| 177 | |||
| 178 | /* SSI audio dividers */ | ||
| 179 | #define IMX_SSI_TX_DIV_2 0 | ||
| 180 | #define IMX_SSI_TX_DIV_PSR 1 | ||
| 181 | #define IMX_SSI_TX_DIV_PM 2 | ||
| 182 | #define IMX_SSI_RX_DIV_2 3 | ||
| 183 | #define IMX_SSI_RX_DIV_PSR 4 | ||
| 184 | #define IMX_SSI_RX_DIV_PM 5 | ||
| 185 | |||
| 186 | #define DRV_NAME "imx-ssi" | ||
| 187 | |||
| 188 | #include <linux/dmaengine.h> | ||
| 189 | #include <mach/dma.h> | ||
| 190 | |||
| 191 | struct imx_pcm_dma_params { | ||
| 192 | int dma; | ||
| 193 | unsigned long dma_addr; | ||
| 194 | int burstsize; | ||
| 195 | }; | ||
| 196 | |||
| 197 | struct imx_ssi { | ||
| 198 | struct platform_device *ac97_dev; | ||
| 199 | |||
| 200 | struct snd_soc_dai *imx_ac97; | ||
| 201 | struct clk *clk; | ||
| 202 | void __iomem *base; | ||
| 203 | int irq; | ||
| 204 | int fiq_enable; | ||
| 205 | unsigned int offset; | ||
| 206 | |||
| 207 | unsigned int flags; | ||
| 208 | |||
| 209 | void (*ac97_reset) (struct snd_ac97 *ac97); | ||
| 210 | void (*ac97_warm_reset)(struct snd_ac97 *ac97); | ||
| 211 | |||
| 212 | struct imx_pcm_dma_params dma_params_rx; | ||
| 213 | struct imx_pcm_dma_params dma_params_tx; | ||
| 214 | |||
| 215 | int enabled; | ||
| 216 | |||
| 217 | struct platform_device *soc_platform_pdev; | ||
| 218 | struct platform_device *soc_platform_pdev_fiq; | ||
| 219 | }; | ||
| 220 | |||
| 221 | struct snd_soc_platform *imx_ssi_fiq_init(struct platform_device *pdev, | ||
| 222 | struct imx_ssi *ssi); | ||
| 223 | void imx_ssi_fiq_exit(struct platform_device *pdev, struct imx_ssi *ssi); | ||
| 224 | struct snd_soc_platform *imx_ssi_dma_mx2_init(struct platform_device *pdev, | ||
| 225 | struct imx_ssi *ssi); | ||
| 226 | |||
| 227 | int snd_imx_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma); | ||
| 228 | int imx_pcm_new(struct snd_soc_pcm_runtime *rtd); | ||
| 229 | void imx_pcm_free(struct snd_pcm *pcm); | ||
| 230 | |||
| 231 | /* | ||
| 232 | * Do not change this as the FIQ handler depends on this size | ||
| 233 | */ | ||
| 234 | #define IMX_SSI_DMABUF_SIZE (64 * 1024) | ||
| 235 | |||
| 236 | #endif /* _IMX_SSI_H */ | ||
diff --git a/sound/soc/imx/mx27vis-aic32x4.c b/sound/soc/imx/mx27vis-aic32x4.c new file mode 100644 index 00000000000..054110b91d4 --- /dev/null +++ b/sound/soc/imx/mx27vis-aic32x4.c | |||
| @@ -0,0 +1,137 @@ | |||
| 1 | /* | ||
| 2 | * mx27vis-aic32x4.c | ||
| 3 | * | ||
| 4 | * Copyright 2011 Vista Silicon S.L. | ||
| 5 | * | ||
| 6 | * Author: Javier Martin <javier.martin@vista-silicon.com> | ||
| 7 | * | ||
| 8 | * This program is free software; you can redistribute it and/or modify it | ||
| 9 | * under the terms of the GNU General Public License as published by the | ||
| 10 | * Free Software Foundation; either version 2 of the License, or (at your | ||
| 11 | * option) any later version. | ||
| 12 | * | ||
| 13 | * This program is distributed in the hope that it will be useful, | ||
| 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 16 | * GNU General Public License for more details. | ||
| 17 | * | ||
| 18 | * You should have received a copy of the GNU General Public License | ||
| 19 | * along with this program; if not, write to the Free Software | ||
| 20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | ||
| 21 | * MA 02110-1301, USA. | ||
| 22 | */ | ||
| 23 | |||
| 24 | #include <linux/module.h> | ||
| 25 | #include <linux/moduleparam.h> | ||
| 26 | #include <linux/device.h> | ||
| 27 | #include <linux/i2c.h> | ||
| 28 | #include <sound/core.h> | ||
| 29 | #include <sound/pcm.h> | ||
| 30 | #include <sound/soc.h> | ||
| 31 | #include <sound/soc-dapm.h> | ||
| 32 | #include <asm/mach-types.h> | ||
| 33 | #include <mach/audmux.h> | ||
| 34 | |||
| 35 | #include "../codecs/tlv320aic32x4.h" | ||
| 36 | #include "imx-ssi.h" | ||
| 37 | |||
| 38 | static int mx27vis_aic32x4_hw_params(struct snd_pcm_substream *substream, | ||
| 39 | struct snd_pcm_hw_params *params) | ||
| 40 | { | ||
| 41 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
| 42 | struct snd_soc_dai *codec_dai = rtd->codec_dai; | ||
| 43 | struct snd_soc_dai *cpu_dai = rtd->cpu_dai; | ||
| 44 | int ret; | ||
| 45 | u32 dai_format; | ||
| 46 | |||
| 47 | dai_format = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | | ||
| 48 | SND_SOC_DAIFMT_CBM_CFM; | ||
| 49 | |||
| 50 | /* set codec DAI configuration */ | ||
| 51 | snd_soc_dai_set_fmt(codec_dai, dai_format); | ||
| 52 | |||
| 53 | /* set cpu DAI configuration */ | ||
| 54 | snd_soc_dai_set_fmt(cpu_dai, dai_format); | ||
| 55 | |||
| 56 | ret = snd_soc_dai_set_sysclk(codec_dai, 0, | ||
| 57 | 25000000, SND_SOC_CLOCK_OUT); | ||
| 58 | if (ret) { | ||
| 59 | pr_err("%s: failed setting codec sysclk\n", __func__); | ||
| 60 | return ret; | ||
| 61 | } | ||
| 62 | |||
| 63 | ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, | ||
| 64 | SND_SOC_CLOCK_IN); | ||
| 65 | if (ret) { | ||
| 66 | pr_err("can't set CPU system clock IMX_SSP_SYS_CLK\n"); | ||
| 67 | return ret; | ||
| 68 | } | ||
| 69 | |||
| 70 | return 0; | ||
| 71 | } | ||
| 72 | |||
| 73 | static struct snd_soc_ops mx27vis_aic32x4_snd_ops = { | ||
| 74 | .hw_params = mx27vis_aic32x4_hw_params, | ||
| 75 | }; | ||
| 76 | |||
| 77 | static struct snd_soc_dai_link mx27vis_aic32x4_dai = { | ||
| 78 | .name = "tlv320aic32x4", | ||
| 79 | .stream_name = "TLV320AIC32X4", | ||
| 80 | .codec_dai_name = "tlv320aic32x4-hifi", | ||
| 81 | .platform_name = "imx-pcm-audio.0", | ||
| 82 | .codec_name = "tlv320aic32x4.0-0018", | ||
| 83 | .cpu_dai_name = "imx-ssi.0", | ||
| 84 | .ops = &mx27vis_aic32x4_snd_ops, | ||
| 85 | }; | ||
| 86 | |||
| 87 | static struct snd_soc_card mx27vis_aic32x4 = { | ||
| 88 | .name = "visstrim_m10-audio", | ||
| 89 | .dai_link = &mx27vis_aic32x4_dai, | ||
| 90 | .num_links = 1, | ||
| 91 | }; | ||
| 92 | |||
| 93 | static struct platform_device *mx27vis_aic32x4_snd_device; | ||
| 94 | |||
| 95 | static int __init mx27vis_aic32x4_init(void) | ||
| 96 | { | ||
| 97 | int ret; | ||
| 98 | |||
| 99 | mx27vis_aic32x4_snd_device = platform_device_alloc("soc-audio", -1); | ||
| 100 | if (!mx27vis_aic32x4_snd_device) | ||
| 101 | return -ENOMEM; | ||
| 102 | |||
| 103 | platform_set_drvdata(mx27vis_aic32x4_snd_device, &mx27vis_aic32x4); | ||
| 104 | ret = platform_device_add(mx27vis_aic32x4_snd_device); | ||
| 105 | |||
| 106 | if (ret) { | ||
| 107 | printk(KERN_ERR "ASoC: Platform device allocation failed\n"); | ||
| 108 | platform_device_put(mx27vis_aic32x4_snd_device); | ||
| 109 | } | ||
| 110 | |||
| 111 | /* Connect SSI0 as clock slave to SSI1 external pins */ | ||
| 112 | mxc_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0, | ||
| 113 | MXC_AUDMUX_V1_PCR_SYN | | ||
| 114 | MXC_AUDMUX_V1_PCR_TFSDIR | | ||
| 115 | MXC_AUDMUX_V1_PCR_TCLKDIR | | ||
| 116 | MXC_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1) | | ||
| 117 | MXC_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1) | ||
| 118 | ); | ||
| 119 | mxc_audmux_v1_configure_port(MX27_AUDMUX_PPCR1_SSI_PINS_1, | ||
| 120 | MXC_AUDMUX_V1_PCR_SYN | | ||
| 121 | MXC_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0) | ||
| 122 | ); | ||
| 123 | |||
| 124 | return ret; | ||
| 125 | } | ||
| 126 | |||
| 127 | static void __exit mx27vis_aic32x4_exit(void) | ||
| 128 | { | ||
| 129 | platform_device_unregister(mx27vis_aic32x4_snd_device); | ||
| 130 | } | ||
| 131 | |||
| 132 | module_init(mx27vis_aic32x4_init); | ||
| 133 | module_exit(mx27vis_aic32x4_exit); | ||
| 134 | |||
| 135 | MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com>"); | ||
| 136 | MODULE_DESCRIPTION("ALSA SoC AIC32X4 mx27 visstrim"); | ||
| 137 | MODULE_LICENSE("GPL"); | ||
diff --git a/sound/soc/imx/phycore-ac97.c b/sound/soc/imx/phycore-ac97.c new file mode 100644 index 00000000000..a7deb5cb243 --- /dev/null +++ b/sound/soc/imx/phycore-ac97.c | |||
| @@ -0,0 +1,99 @@ | |||
| 1 | /* | ||
| 2 | * phycore-ac97.c -- SoC audio for imx_phycore in AC97 mode | ||
| 3 | * | ||
| 4 | * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> | ||
| 5 | * | ||
| 6 | * This program is free software; you can redistribute it and/or modify it | ||
| 7 | * under the terms of the GNU General Public License as published by the | ||
| 8 | * Free Software Foundation; either version 2 of the License, or (at your | ||
| 9 | * option) any later version. | ||
| 10 | * | ||
| 11 | */ | ||
| 12 | |||
| 13 | #include <linux/module.h> | ||
| 14 | #include <linux/moduleparam.h> | ||
| 15 | #include <linux/device.h> | ||
| 16 | #include <linux/i2c.h> | ||
| 17 | #include <sound/core.h> | ||
| 18 | #include <sound/pcm.h> | ||
| 19 | #include <sound/soc.h> | ||
| 20 | #include <asm/mach-types.h> | ||
| 21 | |||
| 22 | static struct snd_soc_card imx_phycore; | ||
| 23 | |||
| 24 | static struct snd_soc_ops imx_phycore_hifi_ops = { | ||
| 25 | }; | ||
| 26 | |||
| 27 | static struct snd_soc_dai_link imx_phycore_dai_ac97[] = { | ||
| 28 | { | ||
| 29 | .name = "HiFi", | ||
| 30 | .stream_name = "HiFi", | ||
| 31 | .codec_dai_name = "wm9712-hifi", | ||
| 32 | .codec_name = "wm9712-codec", | ||
| 33 | .cpu_dai_name = "imx-ssi.0", | ||
| 34 | .platform_name = "imx-fiq-pcm-audio.0", | ||
| 35 | .ops = &imx_phycore_hifi_ops, | ||
| 36 | }, | ||
| 37 | }; | ||
| 38 | |||
| 39 | static struct snd_soc_card imx_phycore = { | ||
| 40 | .name = "PhyCORE-ac97-audio", | ||
| 41 | .dai_link = imx_phycore_dai_ac97, | ||
| 42 | .num_links = ARRAY_SIZE(imx_phycore_dai_ac97), | ||
| 43 | }; | ||
| 44 | |||
| 45 | static struct platform_device *imx_phycore_snd_ac97_device; | ||
| 46 | static struct platform_device *imx_phycore_snd_device; | ||
| 47 | |||
| 48 | static int __init imx_phycore_init(void) | ||
| 49 | { | ||
| 50 | int ret; | ||
| 51 | |||
| 52 | if (!machine_is_pcm043() && !machine_is_pca100()) | ||
| 53 | /* return happy. We might run on a totally different machine */ | ||
| 54 | return 0; | ||
| 55 | |||
| 56 | imx_phycore_snd_ac97_device = platform_device_alloc("soc-audio", -1); | ||
| 57 | if (!imx_phycore_snd_ac97_device) | ||
| 58 | return -ENOMEM; | ||
| 59 | |||
| 60 | platform_set_drvdata(imx_phycore_snd_ac97_device, &imx_phycore); | ||
| 61 | ret = platform_device_add(imx_phycore_snd_ac97_device); | ||
| 62 | if (ret) | ||
| 63 | goto fail1; | ||
| 64 | |||
| 65 | imx_phycore_snd_device = platform_device_alloc("wm9712-codec", -1); | ||
| 66 | if (!imx_phycore_snd_device) { | ||
| 67 | ret = -ENOMEM; | ||
| 68 | goto fail2; | ||
| 69 | } | ||
| 70 | ret = platform_device_add(imx_phycore_snd_device); | ||
| 71 | |||
| 72 | if (ret) { | ||
| 73 | printk(KERN_ERR "ASoC: Platform device allocation failed\n"); | ||
| 74 | goto fail3; | ||
| 75 | } | ||
| 76 | |||
| 77 | return 0; | ||
| 78 | |||
| 79 | fail3: | ||
| 80 | platform_device_put(imx_phycore_snd_device); | ||
| 81 | fail2: | ||
| 82 | platform_device_del(imx_phycore_snd_ac97_device); | ||
| 83 | fail1: | ||
| 84 | platform_device_put(imx_phycore_snd_ac97_device); | ||
| 85 | return ret; | ||
| 86 | } | ||
| 87 | |||
| 88 | static void __exit imx_phycore_exit(void) | ||
| 89 | { | ||
| 90 | platform_device_unregister(imx_phycore_snd_device); | ||
| 91 | platform_device_unregister(imx_phycore_snd_ac97_device); | ||
| 92 | } | ||
| 93 | |||
| 94 | late_initcall(imx_phycore_init); | ||
| 95 | module_exit(imx_phycore_exit); | ||
| 96 | |||
| 97 | MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); | ||
| 98 | MODULE_DESCRIPTION("PhyCORE ALSA SoC driver"); | ||
| 99 | MODULE_LICENSE("GPL"); | ||
diff --git a/sound/soc/imx/wm1133-ev1.c b/sound/soc/imx/wm1133-ev1.c new file mode 100644 index 00000000000..75b4c72787e --- /dev/null +++ b/sound/soc/imx/wm1133-ev1.c | |||
| @@ -0,0 +1,303 @@ | |||
| 1 | /* | ||
| 2 | * wm1133-ev1.c - Audio for WM1133-EV1 on i.MX31ADS | ||
| 3 | * | ||
| 4 | * Copyright (c) 2010 Wolfson Microelectronics plc | ||
| 5 | * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> | ||
| 6 | * | ||
| 7 | * Based on an earlier driver for the same hardware by Liam Girdwood. | ||
| 8 | * | ||
| 9 | * This program is free software; you can redistribute it and/or modify it | ||
| 10 | * under the terms of the GNU General Public License as published by the | ||
| 11 | * Free Software Foundation; either version 2 of the License, or (at your | ||
| 12 | * option) any later version. | ||
| 13 | */ | ||
| 14 | |||
| 15 | #include <linux/platform_device.h> | ||
| 16 | #include <linux/clk.h> | ||
| 17 | #include <sound/core.h> | ||
| 18 | #include <sound/jack.h> | ||
| 19 | #include <sound/pcm.h> | ||
| 20 | #include <sound/pcm_params.h> | ||
| 21 | #include <sound/soc.h> | ||
| 22 | |||
| 23 | #include <mach/audmux.h> | ||
| 24 | |||
| 25 | #include "imx-ssi.h" | ||
| 26 | #include "../codecs/wm8350.h" | ||
| 27 | |||
| 28 | /* There is a silicon mic on the board optionally connected via a solder pad | ||
| 29 | * SP1. Define this to enable it. | ||
| 30 | */ | ||
| 31 | #undef USE_SIMIC | ||
| 32 | |||
| 33 | struct _wm8350_audio { | ||
| 34 | unsigned int channels; | ||
| 35 | snd_pcm_format_t format; | ||
| 36 | unsigned int rate; | ||
| 37 | unsigned int sysclk; | ||
| 38 | unsigned int bclkdiv; | ||
| 39 | unsigned int clkdiv; | ||
| 40 | unsigned int lr_rate; | ||
| 41 | }; | ||
| 42 | |||
| 43 | /* in order of power consumption per rate (lowest first) */ | ||
| 44 | static const struct _wm8350_audio wm8350_audio[] = { | ||
| 45 | /* 16bit mono modes */ | ||
| 46 | {1, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000 >> 1, | ||
| 47 | WM8350_BCLK_DIV_48, WM8350_DACDIV_3, 16,}, | ||
| 48 | |||
| 49 | /* 16 bit stereo modes */ | ||
| 50 | {2, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000, | ||
| 51 | WM8350_BCLK_DIV_48, WM8350_DACDIV_6, 32,}, | ||
| 52 | {2, SNDRV_PCM_FORMAT_S16_LE, 16000, 12288000, | ||
| 53 | WM8350_BCLK_DIV_24, WM8350_DACDIV_3, 32,}, | ||
| 54 | {2, SNDRV_PCM_FORMAT_S16_LE, 32000, 12288000, | ||
| 55 | WM8350_BCLK_DIV_12, WM8350_DACDIV_1_5, 32,}, | ||
| 56 | {2, SNDRV_PCM_FORMAT_S16_LE, 48000, 12288000, | ||
| 57 | WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, | ||
| 58 | {2, SNDRV_PCM_FORMAT_S16_LE, 96000, 24576000, | ||
| 59 | WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, | ||
| 60 | {2, SNDRV_PCM_FORMAT_S16_LE, 11025, 11289600, | ||
| 61 | WM8350_BCLK_DIV_32, WM8350_DACDIV_4, 32,}, | ||
| 62 | {2, SNDRV_PCM_FORMAT_S16_LE, 22050, 11289600, | ||
| 63 | WM8350_BCLK_DIV_16, WM8350_DACDIV_2, 32,}, | ||
| 64 | {2, SNDRV_PCM_FORMAT_S16_LE, 44100, 11289600, | ||
| 65 | WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, | ||
| 66 | {2, SNDRV_PCM_FORMAT_S16_LE, 88200, 22579200, | ||
| 67 | WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,}, | ||
| 68 | |||
| 69 | /* 24bit stereo modes */ | ||
| 70 | {2, SNDRV_PCM_FORMAT_S24_LE, 48000, 12288000, | ||
| 71 | WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, | ||
| 72 | {2, SNDRV_PCM_FORMAT_S24_LE, 96000, 24576000, | ||
| 73 | WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, | ||
| 74 | {2, SNDRV_PCM_FORMAT_S24_LE, 44100, 11289600, | ||
| 75 | WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, | ||
| 76 | {2, SNDRV_PCM_FORMAT_S24_LE, 88200, 22579200, | ||
| 77 | WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,}, | ||
| 78 | }; | ||
| 79 | |||
| 80 | static int wm1133_ev1_hw_params(struct snd_pcm_substream *substream, | ||
| 81 | struct snd_pcm_hw_params *params) | ||
| 82 | { | ||
| 83 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
| 84 | struct snd_soc_dai *codec_dai = rtd->codec_dai; | ||
| 85 | struct snd_soc_dai *cpu_dai = rtd->cpu_dai; | ||
| 86 | int i, found = 0; | ||
| 87 | snd_pcm_format_t format = params_format(params); | ||
| 88 | unsigned int rate = params_rate(params); | ||
| 89 | unsigned int channels = params_channels(params); | ||
| 90 | u32 dai_format; | ||
| 91 | |||
| 92 | /* find the correct audio parameters */ | ||
| 93 | for (i = 0; i < ARRAY_SIZE(wm8350_audio); i++) { | ||
| 94 | if (rate == wm8350_audio[i].rate && | ||
| 95 | format == wm8350_audio[i].format && | ||
| 96 | channels == wm8350_audio[i].channels) { | ||
| 97 | found = 1; | ||
| 98 | break; | ||
| 99 | } | ||
| 100 | } | ||
| 101 | if (!found) | ||
| 102 | return -EINVAL; | ||
| 103 | |||
| 104 | /* codec FLL input is 14.75 MHz from MCLK */ | ||
| 105 | snd_soc_dai_set_pll(codec_dai, 0, 0, 14750000, wm8350_audio[i].sysclk); | ||
| 106 | |||
| 107 | dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | | ||
| 108 | SND_SOC_DAIFMT_CBM_CFM; | ||
| 109 | |||
| 110 | /* set codec DAI configuration */ | ||
| 111 | snd_soc_dai_set_fmt(codec_dai, dai_format); | ||
| 112 | |||
| 113 | /* set cpu DAI configuration */ | ||
| 114 | snd_soc_dai_set_fmt(cpu_dai, dai_format); | ||
| 115 | |||
| 116 | /* TODO: The SSI driver should figure this out for us */ | ||
| 117 | switch (channels) { | ||
| 118 | case 2: | ||
| 119 | snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffc, 0xffffffc, 2, 0); | ||
| 120 | break; | ||
| 121 | case 1: | ||
| 122 | snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffe, 0xffffffe, 1, 0); | ||
| 123 | break; | ||
| 124 | default: | ||
| 125 | return -EINVAL; | ||
| 126 | } | ||
| 127 | |||
| 128 | /* set MCLK as the codec system clock for DAC and ADC */ | ||
| 129 | snd_soc_dai_set_sysclk(codec_dai, WM8350_MCLK_SEL_PLL_MCLK, | ||
| 130 | wm8350_audio[i].sysclk, SND_SOC_CLOCK_IN); | ||
| 131 | |||
| 132 | /* set codec BCLK division for sample rate */ | ||
| 133 | snd_soc_dai_set_clkdiv(codec_dai, WM8350_BCLK_CLKDIV, | ||
| 134 | wm8350_audio[i].bclkdiv); | ||
| 135 | |||
| 136 | /* DAI is synchronous and clocked with DAC LRCLK & ADC LRC */ | ||
| 137 | snd_soc_dai_set_clkdiv(codec_dai, | ||
| 138 | WM8350_DACLR_CLKDIV, wm8350_audio[i].lr_rate); | ||
| 139 | snd_soc_dai_set_clkdiv(codec_dai, | ||
| 140 | WM8350_ADCLR_CLKDIV, wm8350_audio[i].lr_rate); | ||
| 141 | |||
| 142 | /* now configure DAC and ADC clocks */ | ||
| 143 | snd_soc_dai_set_clkdiv(codec_dai, | ||
| 144 | WM8350_DAC_CLKDIV, wm8350_audio[i].clkdiv); | ||
| 145 | |||
| 146 | snd_soc_dai_set_clkdiv(codec_dai, | ||
| 147 | WM8350_ADC_CLKDIV, wm8350_audio[i].clkdiv); | ||
| 148 | |||
| 149 | return 0; | ||
| 150 | } | ||
| 151 | |||
| 152 | static struct snd_soc_ops wm1133_ev1_ops = { | ||
| 153 | .hw_params = wm1133_ev1_hw_params, | ||
| 154 | }; | ||
| 155 | |||
| 156 | static const struct snd_soc_dapm_widget wm1133_ev1_widgets[] = { | ||
| 157 | #ifdef USE_SIMIC | ||
| 158 | SND_SOC_DAPM_MIC("SiMIC", NULL), | ||
| 159 | #endif | ||
| 160 | SND_SOC_DAPM_MIC("Mic1 Jack", NULL), | ||
| 161 | SND_SOC_DAPM_MIC("Mic2 Jack", NULL), | ||
| 162 | SND_SOC_DAPM_LINE("Line In Jack", NULL), | ||
| 163 | SND_SOC_DAPM_LINE("Line Out Jack", NULL), | ||
| 164 | SND_SOC_DAPM_HP("Headphone Jack", NULL), | ||
| 165 | }; | ||
| 166 | |||
| 167 | /* imx32ads soc_card audio map */ | ||
| 168 | static const struct snd_soc_dapm_route wm1133_ev1_map[] = { | ||
| 169 | |||
| 170 | #ifdef USE_SIMIC | ||
| 171 | /* SiMIC --> IN1LN (with automatic bias) via SP1 */ | ||
| 172 | { "IN1LN", NULL, "Mic Bias" }, | ||
| 173 | { "Mic Bias", NULL, "SiMIC" }, | ||
| 174 | #endif | ||
| 175 | |||
| 176 | /* Mic 1 Jack --> IN1LN and IN1LP (with automatic bias) */ | ||
| 177 | { "IN1LN", NULL, "Mic Bias" }, | ||
| 178 | { "IN1LP", NULL, "Mic1 Jack" }, | ||
| 179 | { "Mic Bias", NULL, "Mic1 Jack" }, | ||
| 180 | |||
| 181 | /* Mic 2 Jack --> IN1RN and IN1RP (with automatic bias) */ | ||
| 182 | { "IN1RN", NULL, "Mic Bias" }, | ||
| 183 | { "IN1RP", NULL, "Mic2 Jack" }, | ||
| 184 | { "Mic Bias", NULL, "Mic2 Jack" }, | ||
| 185 | |||
| 186 | /* Line in Jack --> AUX (L+R) */ | ||
| 187 | { "IN3R", NULL, "Line In Jack" }, | ||
| 188 | { "IN3L", NULL, "Line In Jack" }, | ||
| 189 | |||
| 190 | /* Out1 --> Headphone Jack */ | ||
| 191 | { "Headphone Jack", NULL, "OUT1R" }, | ||
| 192 | { "Headphone Jack", NULL, "OUT1L" }, | ||
| 193 | |||
| 194 | /* Out1 --> Line Out Jack */ | ||
| 195 | { "Line Out Jack", NULL, "OUT2R" }, | ||
| 196 | { "Line Out Jack", NULL, "OUT2L" }, | ||
| 197 | }; | ||
| 198 | |||
| 199 | static struct snd_soc_jack hp_jack; | ||
| 200 | |||
| 201 | static struct snd_soc_jack_pin hp_jack_pins[] = { | ||
| 202 | { .pin = "Headphone Jack", .mask = SND_JACK_HEADPHONE }, | ||
| 203 | }; | ||
| 204 | |||
| 205 | static struct snd_soc_jack mic_jack; | ||
| 206 | |||
| 207 | static struct snd_soc_jack_pin mic_jack_pins[] = { | ||
| 208 | { .pin = "Mic1 Jack", .mask = SND_JACK_MICROPHONE }, | ||
| 209 | { .pin = "Mic2 Jack", .mask = SND_JACK_MICROPHONE }, | ||
| 210 | }; | ||
| 211 | |||
| 212 | static int wm1133_ev1_init(struct snd_soc_pcm_runtime *rtd) | ||
| 213 | { | ||
| 214 | struct snd_soc_codec *codec = rtd->codec; | ||
| 215 | struct snd_soc_dapm_context *dapm = &codec->dapm; | ||
| 216 | |||
| 217 | snd_soc_dapm_new_controls(dapm, wm1133_ev1_widgets, | ||
| 218 | ARRAY_SIZE(wm1133_ev1_widgets)); | ||
| 219 | |||
| 220 | snd_soc_dapm_add_routes(dapm, wm1133_ev1_map, | ||
| 221 | ARRAY_SIZE(wm1133_ev1_map)); | ||
| 222 | |||
| 223 | /* Headphone jack detection */ | ||
| 224 | snd_soc_jack_new(codec, "Headphone", SND_JACK_HEADPHONE, &hp_jack); | ||
| 225 | snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins), | ||
| 226 | hp_jack_pins); | ||
| 227 | wm8350_hp_jack_detect(codec, WM8350_JDR, &hp_jack, SND_JACK_HEADPHONE); | ||
| 228 | |||
| 229 | /* Microphone jack detection */ | ||
| 230 | snd_soc_jack_new(codec, "Microphone", | ||
| 231 | SND_JACK_MICROPHONE | SND_JACK_BTN_0, &mic_jack); | ||
| 232 | snd_soc_jack_add_pins(&mic_jack, ARRAY_SIZE(mic_jack_pins), | ||
| 233 | mic_jack_pins); | ||
| 234 | wm8350_mic_jack_detect(codec, &mic_jack, SND_JACK_MICROPHONE, | ||
| 235 | SND_JACK_BTN_0); | ||
| 236 | |||
| 237 | snd_soc_dapm_force_enable_pin(dapm, "Mic Bias"); | ||
| 238 | |||
| 239 | return 0; | ||
| 240 | } | ||
| 241 | |||
| 242 | |||
| 243 | static struct snd_soc_dai_link wm1133_ev1_dai = { | ||
| 244 | .name = "WM1133-EV1", | ||
| 245 | .stream_name = "Audio", | ||
| 246 | .cpu_dai_name = "imx-ssi.0", | ||
| 247 | .codec_dai_name = "wm8350-hifi", | ||
| 248 | .platform_name = "imx-fiq-pcm-audio.0", | ||
| 249 | .codec_name = "wm8350-codec.0-0x1a", | ||
| 250 | .init = wm1133_ev1_init, | ||
| 251 | .ops = &wm1133_ev1_ops, | ||
| 252 | .symmetric_rates = 1, | ||
| 253 | }; | ||
| 254 | |||
| 255 | static struct snd_soc_card wm1133_ev1 = { | ||
| 256 | .name = "WM1133-EV1", | ||
| 257 | .dai_link = &wm1133_ev1_dai, | ||
| 258 | .num_links = 1, | ||
| 259 | }; | ||
| 260 | |||
| 261 | static struct platform_device *wm1133_ev1_snd_device; | ||
| 262 | |||
| 263 | static int __init wm1133_ev1_audio_init(void) | ||
| 264 | { | ||
| 265 | int ret; | ||
| 266 | unsigned int ptcr, pdcr; | ||
| 267 | |||
| 268 | /* SSI0 mastered by port 5 */ | ||
| 269 | ptcr = MXC_AUDMUX_V2_PTCR_SYN | | ||
| 270 | MXC_AUDMUX_V2_PTCR_TFSDIR | | ||
| 271 | MXC_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT5_SSI_PINS_5) | | ||
| 272 | MXC_AUDMUX_V2_PTCR_TCLKDIR | | ||
| 273 | MXC_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT5_SSI_PINS_5); | ||
| 274 | pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT5_SSI_PINS_5); | ||
| 275 | mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0, ptcr, pdcr); | ||
| 276 | |||
| 277 | ptcr = MXC_AUDMUX_V2_PTCR_SYN; | ||
| 278 | pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0); | ||
| 279 | mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT5_SSI_PINS_5, ptcr, pdcr); | ||
| 280 | |||
| 281 | wm1133_ev1_snd_device = platform_device_alloc("soc-audio", -1); | ||
| 282 | if (!wm1133_ev1_snd_device) | ||
| 283 | return -ENOMEM; | ||
| 284 | |||
| 285 | platform_set_drvdata(wm1133_ev1_snd_device, &wm1133_ev1); | ||
| 286 | ret = platform_device_add(wm1133_ev1_snd_device); | ||
| 287 | |||
| 288 | if (ret) | ||
| 289 | platform_device_put(wm1133_ev1_snd_device); | ||
| 290 | |||
| 291 | return ret; | ||
| 292 | } | ||
| 293 | module_init(wm1133_ev1_audio_init); | ||
| 294 | |||
| 295 | static void __exit wm1133_ev1_audio_exit(void) | ||
| 296 | { | ||
| 297 | platform_device_unregister(wm1133_ev1_snd_device); | ||
| 298 | } | ||
| 299 | module_exit(wm1133_ev1_audio_exit); | ||
| 300 | |||
| 301 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); | ||
| 302 | MODULE_DESCRIPTION("Audio for WM1133-EV1 on i.MX31ADS"); | ||
| 303 | MODULE_LICENSE("GPL"); | ||
