diff options
author | Bo Shen <voice.shen@atmel.com> | 2012-11-27 22:46:13 -0500 |
---|---|---|
committer | Mark Brown <broonie@opensource.wolfsonmicro.com> | 2012-12-01 23:29:19 -0500 |
commit | 3951e4aae2ce7e4593e575e91cbb22f1ba153596 (patch) | |
tree | f210f5e34e8172e1d8c5b2aa96db70b9a2d16922 /sound/soc/atmel | |
parent | 92dfa61986237f849bb6ff5190380b3bf69335d2 (diff) |
ASoC: atmel-pcm: dma support based on pcm dmaengine
Specify overrun bit in interrupt mask
Add dmaengine specific routines
Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com>
[voice.shen@atmel.com: adapt to soc dmaengine framework]
Signed-off-by: Bo Shen <voice.shen@atmel.com>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/atmel')
-rw-r--r-- | sound/soc/atmel/Kconfig | 5 | ||||
-rw-r--r-- | sound/soc/atmel/Makefile | 2 | ||||
-rw-r--r-- | sound/soc/atmel/atmel-pcm-dma.c | 240 | ||||
-rw-r--r-- | sound/soc/atmel/atmel-pcm.h | 14 | ||||
-rw-r--r-- | sound/soc/atmel/atmel_ssc_dai.c | 17 |
5 files changed, 276 insertions, 2 deletions
diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig index 6051993eca91..d1b691bf8e2d 100644 --- a/sound/soc/atmel/Kconfig +++ b/sound/soc/atmel/Kconfig | |||
@@ -10,6 +10,10 @@ config SND_ATMEL_SOC_PDC | |||
10 | tristate | 10 | tristate |
11 | depends on SND_ATMEL_SOC | 11 | depends on SND_ATMEL_SOC |
12 | 12 | ||
13 | config SND_ATMEL_SOC_DMA | ||
14 | tristate | ||
15 | depends on SND_ATMEL_SOC | ||
16 | |||
13 | config SND_ATMEL_SOC_SSC | 17 | config SND_ATMEL_SOC_SSC |
14 | tristate | 18 | tristate |
15 | depends on SND_ATMEL_SOC | 19 | depends on SND_ATMEL_SOC |
@@ -31,6 +35,7 @@ config SND_AT91_SOC_SAM9G20_WM8731 | |||
31 | config SND_AT91_SOC_AFEB9260 | 35 | config SND_AT91_SOC_AFEB9260 |
32 | tristate "SoC Audio support for AFEB9260 board" | 36 | tristate "SoC Audio support for AFEB9260 board" |
33 | depends on ATMEL_SSC && ARCH_AT91 && MACH_AFEB9260 && SND_ATMEL_SOC | 37 | depends on ATMEL_SSC && ARCH_AT91 && MACH_AFEB9260 && SND_ATMEL_SOC |
38 | select SND_ATMEL_SOC_PDC | ||
34 | select SND_ATMEL_SOC_SSC | 39 | select SND_ATMEL_SOC_SSC |
35 | select SND_SOC_TLV320AIC23 | 40 | select SND_SOC_TLV320AIC23 |
36 | help | 41 | help |
diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile index 2e37864ee860..41967ccb6f41 100644 --- a/sound/soc/atmel/Makefile +++ b/sound/soc/atmel/Makefile | |||
@@ -1,10 +1,12 @@ | |||
1 | # AT91 Platform Support | 1 | # AT91 Platform Support |
2 | snd-soc-atmel-pcm-objs := atmel-pcm.o | 2 | snd-soc-atmel-pcm-objs := atmel-pcm.o |
3 | snd-soc-atmel-pcm-pdc-objs := atmel-pcm-pdc.o | 3 | snd-soc-atmel-pcm-pdc-objs := atmel-pcm-pdc.o |
4 | snd-soc-atmel-pcm-dma-objs := atmel-pcm-dma.o | ||
4 | snd-soc-atmel_ssc_dai-objs := atmel_ssc_dai.o | 5 | snd-soc-atmel_ssc_dai-objs := atmel_ssc_dai.o |
5 | 6 | ||
6 | obj-$(CONFIG_SND_ATMEL_SOC) += snd-soc-atmel-pcm.o | 7 | obj-$(CONFIG_SND_ATMEL_SOC) += snd-soc-atmel-pcm.o |
7 | obj-$(CONFIG_SND_ATMEL_SOC_PDC) += snd-soc-atmel-pcm-pdc.o | 8 | obj-$(CONFIG_SND_ATMEL_SOC_PDC) += snd-soc-atmel-pcm-pdc.o |
9 | obj-$(CONFIG_SND_ATMEL_SOC_DMA) += snd-soc-atmel-pcm-dma.o | ||
8 | obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o | 10 | obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o |
9 | 11 | ||
10 | # AT91 Machine Support | 12 | # AT91 Machine Support |
diff --git a/sound/soc/atmel/atmel-pcm-dma.c b/sound/soc/atmel/atmel-pcm-dma.c new file mode 100644 index 000000000000..30184a4a147a --- /dev/null +++ b/sound/soc/atmel/atmel-pcm-dma.c | |||
@@ -0,0 +1,240 @@ | |||
1 | /* | ||
2 | * atmel-pcm-dma.c -- ALSA PCM DMA support for the Atmel SoC. | ||
3 | * | ||
4 | * Copyright (C) 2012 Atmel | ||
5 | * | ||
6 | * Author: Bo Shen <voice.shen@atmel.com> | ||
7 | * | ||
8 | * Based on atmel-pcm by: | ||
9 | * Sedji Gaouaou <sedji.gaouaou@atmel.com> | ||
10 | * Copyright 2008 Atmel | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or modify | ||
13 | * it under the terms of the GNU General Public License as published by | ||
14 | * the Free Software Foundation; either version 2 of the License, or | ||
15 | * (at your option) any later version. | ||
16 | * | ||
17 | * This program is distributed in the hope that it will be useful, | ||
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
20 | * GNU General Public License for more details. | ||
21 | * | ||
22 | * You should have received a copy of the GNU General Public License | ||
23 | * along with this program; if not, write to the Free Software | ||
24 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
25 | */ | ||
26 | |||
27 | #include <linux/module.h> | ||
28 | #include <linux/init.h> | ||
29 | #include <linux/platform_device.h> | ||
30 | #include <linux/slab.h> | ||
31 | #include <linux/dma-mapping.h> | ||
32 | #include <linux/dmaengine.h> | ||
33 | #include <linux/atmel-ssc.h> | ||
34 | #include <linux/platform_data/dma-atmel.h> | ||
35 | |||
36 | #include <sound/core.h> | ||
37 | #include <sound/pcm.h> | ||
38 | #include <sound/pcm_params.h> | ||
39 | #include <sound/soc.h> | ||
40 | #include <sound/dmaengine_pcm.h> | ||
41 | |||
42 | #include "atmel-pcm.h" | ||
43 | |||
44 | /*--------------------------------------------------------------------------*\ | ||
45 | * Hardware definition | ||
46 | \*--------------------------------------------------------------------------*/ | ||
47 | static const struct snd_pcm_hardware atmel_pcm_dma_hardware = { | ||
48 | .info = SNDRV_PCM_INFO_MMAP | | ||
49 | SNDRV_PCM_INFO_MMAP_VALID | | ||
50 | SNDRV_PCM_INFO_INTERLEAVED | | ||
51 | SNDRV_PCM_INFO_RESUME | | ||
52 | SNDRV_PCM_INFO_PAUSE, | ||
53 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
54 | .period_bytes_min = 256, /* lighting DMA overhead */ | ||
55 | .period_bytes_max = 2 * 0xffff, /* if 2 bytes format */ | ||
56 | .periods_min = 8, | ||
57 | .periods_max = 1024, /* no limit */ | ||
58 | .buffer_bytes_max = ATMEL_SSC_DMABUF_SIZE, | ||
59 | }; | ||
60 | |||
61 | /** | ||
62 | * atmel_pcm_dma_irq: SSC interrupt handler for DMAENGINE enabled SSC | ||
63 | * | ||
64 | * We use DMAENGINE to send/receive data to/from SSC so this ISR is only to | ||
65 | * check if any overrun occured. | ||
66 | */ | ||
67 | static void atmel_pcm_dma_irq(u32 ssc_sr, | ||
68 | struct snd_pcm_substream *substream) | ||
69 | { | ||
70 | struct atmel_pcm_dma_params *prtd; | ||
71 | |||
72 | prtd = snd_dmaengine_pcm_get_data(substream); | ||
73 | |||
74 | if (ssc_sr & prtd->mask->ssc_error) { | ||
75 | if (snd_pcm_running(substream)) | ||
76 | pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x)\n", | ||
77 | substream->stream == SNDRV_PCM_STREAM_PLAYBACK | ||
78 | ? "underrun" : "overrun", prtd->name, | ||
79 | ssc_sr); | ||
80 | |||
81 | /* stop RX and capture: will be enabled again at restart */ | ||
82 | ssc_writex(prtd->ssc->regs, SSC_CR, prtd->mask->ssc_disable); | ||
83 | snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); | ||
84 | |||
85 | /* now drain RHR and read status to remove xrun condition */ | ||
86 | ssc_readx(prtd->ssc->regs, SSC_RHR); | ||
87 | ssc_readx(prtd->ssc->regs, SSC_SR); | ||
88 | } | ||
89 | } | ||
90 | |||
91 | /*--------------------------------------------------------------------------*\ | ||
92 | * DMAENGINE operations | ||
93 | \*--------------------------------------------------------------------------*/ | ||
94 | static bool filter(struct dma_chan *chan, void *slave) | ||
95 | { | ||
96 | struct at_dma_slave *sl = slave; | ||
97 | |||
98 | if (sl->dma_dev == chan->device->dev) { | ||
99 | chan->private = sl; | ||
100 | return true; | ||
101 | } else { | ||
102 | return false; | ||
103 | } | ||
104 | } | ||
105 | |||
106 | static int atmel_pcm_configure_dma(struct snd_pcm_substream *substream, | ||
107 | struct snd_pcm_hw_params *params) | ||
108 | { | ||
109 | struct atmel_pcm_dma_params *prtd; | ||
110 | struct ssc_device *ssc; | ||
111 | struct dma_chan *dma_chan; | ||
112 | struct dma_slave_config slave_config; | ||
113 | int ret; | ||
114 | |||
115 | prtd = snd_dmaengine_pcm_get_data(substream); | ||
116 | ssc = prtd->ssc; | ||
117 | |||
118 | ret = snd_hwparams_to_dma_slave_config(substream, params, | ||
119 | &slave_config); | ||
120 | if (ret) { | ||
121 | pr_err("atmel-pcm: hwparams to dma slave configure failed\n"); | ||
122 | return ret; | ||
123 | } | ||
124 | |||
125 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
126 | slave_config.dst_addr = (dma_addr_t)ssc->phybase + SSC_THR; | ||
127 | slave_config.dst_maxburst = 1; | ||
128 | } else { | ||
129 | slave_config.src_addr = (dma_addr_t)ssc->phybase + SSC_RHR; | ||
130 | slave_config.src_maxburst = 1; | ||
131 | } | ||
132 | |||
133 | slave_config.device_fc = false; | ||
134 | |||
135 | dma_chan = snd_dmaengine_pcm_get_chan(substream); | ||
136 | if (dmaengine_slave_config(dma_chan, &slave_config)) { | ||
137 | pr_err("atmel-pcm: failed to configure dma channel\n"); | ||
138 | ret = -EBUSY; | ||
139 | return ret; | ||
140 | } | ||
141 | |||
142 | return 0; | ||
143 | } | ||
144 | |||
145 | static int atmel_pcm_hw_params(struct snd_pcm_substream *substream, | ||
146 | struct snd_pcm_hw_params *params) | ||
147 | { | ||
148 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
149 | struct atmel_pcm_dma_params *prtd; | ||
150 | struct ssc_device *ssc; | ||
151 | struct at_dma_slave *sdata = NULL; | ||
152 | int ret; | ||
153 | |||
154 | snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); | ||
155 | |||
156 | prtd = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); | ||
157 | ssc = prtd->ssc; | ||
158 | if (ssc->pdev) | ||
159 | sdata = ssc->pdev->dev.platform_data; | ||
160 | |||
161 | ret = snd_dmaengine_pcm_open(substream, filter, sdata); | ||
162 | if (ret) { | ||
163 | pr_err("atmel-pcm: dmaengine pcm open failed\n"); | ||
164 | return -EINVAL; | ||
165 | } | ||
166 | |||
167 | snd_dmaengine_pcm_set_data(substream, prtd); | ||
168 | |||
169 | ret = atmel_pcm_configure_dma(substream, params); | ||
170 | if (ret) { | ||
171 | pr_err("atmel-pcm: failed to configure dmai\n"); | ||
172 | goto err; | ||
173 | } | ||
174 | |||
175 | prtd->dma_intr_handler = atmel_pcm_dma_irq; | ||
176 | |||
177 | return 0; | ||
178 | err: | ||
179 | snd_dmaengine_pcm_close(substream); | ||
180 | return ret; | ||
181 | } | ||
182 | |||
183 | static int atmel_pcm_dma_prepare(struct snd_pcm_substream *substream) | ||
184 | { | ||
185 | struct atmel_pcm_dma_params *prtd; | ||
186 | |||
187 | prtd = snd_dmaengine_pcm_get_data(substream); | ||
188 | |||
189 | ssc_writex(prtd->ssc->regs, SSC_IER, prtd->mask->ssc_error); | ||
190 | ssc_writex(prtd->ssc->regs, SSC_CR, prtd->mask->ssc_enable); | ||
191 | |||
192 | return 0; | ||
193 | } | ||
194 | |||
195 | static int atmel_pcm_open(struct snd_pcm_substream *substream) | ||
196 | { | ||
197 | snd_soc_set_runtime_hwparams(substream, &atmel_pcm_dma_hardware); | ||
198 | |||
199 | return 0; | ||
200 | } | ||
201 | |||
202 | static int atmel_pcm_close(struct snd_pcm_substream *substream) | ||
203 | { | ||
204 | snd_dmaengine_pcm_close(substream); | ||
205 | |||
206 | return 0; | ||
207 | } | ||
208 | |||
209 | static struct snd_pcm_ops atmel_pcm_ops = { | ||
210 | .open = atmel_pcm_open, | ||
211 | .close = atmel_pcm_close, | ||
212 | .ioctl = snd_pcm_lib_ioctl, | ||
213 | .hw_params = atmel_pcm_hw_params, | ||
214 | .prepare = atmel_pcm_dma_prepare, | ||
215 | .trigger = snd_dmaengine_pcm_trigger, | ||
216 | .pointer = snd_dmaengine_pcm_pointer_no_residue, | ||
217 | .mmap = atmel_pcm_mmap, | ||
218 | }; | ||
219 | |||
220 | static struct snd_soc_platform_driver atmel_soc_platform = { | ||
221 | .ops = &atmel_pcm_ops, | ||
222 | .pcm_new = atmel_pcm_new, | ||
223 | .pcm_free = atmel_pcm_free, | ||
224 | }; | ||
225 | |||
226 | int atmel_pcm_dma_platform_register(struct device *dev) | ||
227 | { | ||
228 | return snd_soc_register_platform(dev, &atmel_soc_platform); | ||
229 | } | ||
230 | EXPORT_SYMBOL(atmel_pcm_dma_platform_register); | ||
231 | |||
232 | void atmel_pcm_dma_platform_unregister(struct device *dev) | ||
233 | { | ||
234 | snd_soc_unregister_platform(dev); | ||
235 | } | ||
236 | EXPORT_SYMBOL(atmel_pcm_dma_platform_unregister); | ||
237 | |||
238 | MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>"); | ||
239 | MODULE_DESCRIPTION("Atmel DMA based PCM module"); | ||
240 | MODULE_LICENSE("GPL"); | ||
diff --git a/sound/soc/atmel/atmel-pcm.h b/sound/soc/atmel/atmel-pcm.h index 6bd63b0e95e2..bb45d20e7250 100644 --- a/sound/soc/atmel/atmel-pcm.h +++ b/sound/soc/atmel/atmel-pcm.h | |||
@@ -52,6 +52,7 @@ struct atmel_pdc_regs { | |||
52 | struct atmel_ssc_mask { | 52 | struct atmel_ssc_mask { |
53 | u32 ssc_enable; /* SSC recv/trans enable */ | 53 | u32 ssc_enable; /* SSC recv/trans enable */ |
54 | u32 ssc_disable; /* SSC recv/trans disable */ | 54 | u32 ssc_disable; /* SSC recv/trans disable */ |
55 | u32 ssc_error; /* SSC error conditions */ | ||
55 | u32 ssc_endx; /* SSC ENDTX or ENDRX */ | 56 | u32 ssc_endx; /* SSC ENDTX or ENDRX */ |
56 | u32 ssc_endbuf; /* SSC TXBUFE or RXBUFF */ | 57 | u32 ssc_endbuf; /* SSC TXBUFE or RXBUFF */ |
57 | u32 pdc_enable; /* PDC recv/trans enable */ | 58 | u32 pdc_enable; /* PDC recv/trans enable */ |
@@ -100,4 +101,17 @@ static inline void atmel_pcm_pdc_platform_unregister(struct device *dev) | |||
100 | } | 101 | } |
101 | #endif | 102 | #endif |
102 | 103 | ||
104 | #ifdef CONFIG_SND_ATMEL_SOC_DMA | ||
105 | int atmel_pcm_dma_platform_register(struct device *dev); | ||
106 | void atmel_pcm_dma_platform_unregister(struct device *dev); | ||
107 | #else | ||
108 | static inline int atmel_pcm_dma_platform_register(struct device *dev) | ||
109 | { | ||
110 | return 0; | ||
111 | } | ||
112 | static inline void atmel_pcm_dma_platform_unregister(struct device *dev) | ||
113 | { | ||
114 | } | ||
115 | #endif | ||
116 | |||
103 | #endif /* _ATMEL_PCM_H */ | 117 | #endif /* _ATMEL_PCM_H */ |
diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c index 170561407f61..1c7663422054 100644 --- a/sound/soc/atmel/atmel_ssc_dai.c +++ b/sound/soc/atmel/atmel_ssc_dai.c | |||
@@ -721,6 +721,8 @@ static struct snd_soc_dai_driver atmel_ssc_dai = { | |||
721 | 721 | ||
722 | static int asoc_ssc_init(struct device *dev) | 722 | static int asoc_ssc_init(struct device *dev) |
723 | { | 723 | { |
724 | struct platform_device *pdev = to_platform_device(dev); | ||
725 | struct ssc_device *ssc = platform_get_drvdata(pdev); | ||
724 | int ret; | 726 | int ret; |
725 | 727 | ||
726 | ret = snd_soc_register_dai(dev, &atmel_ssc_dai); | 728 | ret = snd_soc_register_dai(dev, &atmel_ssc_dai); |
@@ -729,7 +731,11 @@ static int asoc_ssc_init(struct device *dev) | |||
729 | goto err; | 731 | goto err; |
730 | } | 732 | } |
731 | 733 | ||
732 | ret = atmel_pcm_pdc_platform_register(dev); | 734 | if (ssc->pdata->use_dma) |
735 | ret = atmel_pcm_dma_platform_register(dev); | ||
736 | else | ||
737 | ret = atmel_pcm_pdc_platform_register(dev); | ||
738 | |||
733 | if (ret) { | 739 | if (ret) { |
734 | dev_err(dev, "Could not register PCM: %d\n", ret); | 740 | dev_err(dev, "Could not register PCM: %d\n", ret); |
735 | goto err_unregister_dai; | 741 | goto err_unregister_dai; |
@@ -745,7 +751,14 @@ err: | |||
745 | 751 | ||
746 | static void asoc_ssc_exit(struct device *dev) | 752 | static void asoc_ssc_exit(struct device *dev) |
747 | { | 753 | { |
748 | atmel_pcm_pdc_platform_unregister(dev); | 754 | struct platform_device *pdev = to_platform_device(dev); |
755 | struct ssc_device *ssc = platform_get_drvdata(pdev); | ||
756 | |||
757 | if (ssc->pdata->use_dma) | ||
758 | atmel_pcm_dma_platform_unregister(dev); | ||
759 | else | ||
760 | atmel_pcm_pdc_platform_unregister(dev); | ||
761 | |||
749 | snd_soc_unregister_dai(dev); | 762 | snd_soc_unregister_dai(dev); |
750 | } | 763 | } |
751 | 764 | ||