1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32#include <linux/dma-mapping.h>
33#include <linux/module.h>
34#include <linux/slab.h>
35#include <sound/core.h>
36#include <sound/pcm.h>
37#include <sound/pcm_params.h>
38#include <sound/soc.h>
39#include <sound/dmaengine_pcm.h>
40
41#include "tegra_pcm.h"
42
43static const struct snd_pcm_hardware tegra_pcm_hardware = {
44 .info = SNDRV_PCM_INFO_MMAP |
45 SNDRV_PCM_INFO_MMAP_VALID |
46 SNDRV_PCM_INFO_INTERLEAVED,
47 .formats = SNDRV_PCM_FMTBIT_S16_LE,
48 .channels_min = 2,
49 .channels_max = 2,
50 .period_bytes_min = 1024,
51 .period_bytes_max = PAGE_SIZE,
52 .periods_min = 2,
53 .periods_max = 8,
54 .buffer_bytes_max = PAGE_SIZE * 8,
55 .fifo_size = 4,
56};
57
58static int tegra_pcm_open(struct snd_pcm_substream *substream)
59{
60 struct snd_soc_pcm_runtime *rtd = substream->private_data;
61 struct device *dev = rtd->platform->dev;
62 int ret;
63
64
65 snd_soc_set_runtime_hwparams(substream, &tegra_pcm_hardware);
66
67 ret = snd_dmaengine_pcm_open(substream, NULL, NULL);
68 if (ret) {
69 dev_err(dev, "dmaengine pcm open failed with err %d\n", ret);
70 return ret;
71 }
72
73 return 0;
74}
75
76static int tegra_pcm_close(struct snd_pcm_substream *substream)
77{
78 snd_dmaengine_pcm_close(substream);
79 return 0;
80}
81
82static int tegra_pcm_hw_params(struct snd_pcm_substream *substream,
83 struct snd_pcm_hw_params *params)
84{
85 struct snd_soc_pcm_runtime *rtd = substream->private_data;
86 struct device *dev = rtd->platform->dev;
87 struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream);
88 struct tegra_pcm_dma_params *dmap;
89 struct dma_slave_config slave_config;
90 int ret;
91
92 dmap = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
93
94 ret = snd_hwparams_to_dma_slave_config(substream, params,
95 &slave_config);
96 if (ret) {
97 dev_err(dev, "hw params config failed with err %d\n", ret);
98 return ret;
99 }
100
101 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
102 slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
103 slave_config.dst_addr = dmap->addr;
104 slave_config.dst_maxburst = 4;
105 } else {
106 slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
107 slave_config.src_addr = dmap->addr;
108 slave_config.src_maxburst = 4;
109 }
110 slave_config.slave_id = dmap->req_sel;
111
112 ret = dmaengine_slave_config(chan, &slave_config);
113 if (ret < 0) {
114 dev_err(dev, "dma slave config failed with err %d\n", ret);
115 return ret;
116 }
117
118 snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
119 return 0;
120}
121
122static int tegra_pcm_hw_free(struct snd_pcm_substream *substream)
123{
124 snd_pcm_set_runtime_buffer(substream, NULL);
125 return 0;
126}
127
128static int tegra_pcm_mmap(struct snd_pcm_substream *substream,
129 struct vm_area_struct *vma)
130{
131 struct snd_pcm_runtime *runtime = substream->runtime;
132
133 return dma_mmap_writecombine(substream->pcm->card->dev, vma,
134 runtime->dma_area,
135 runtime->dma_addr,
136 runtime->dma_bytes);
137}
138
139static struct snd_pcm_ops tegra_pcm_ops = {
140 .open = tegra_pcm_open,
141 .close = tegra_pcm_close,
142 .ioctl = snd_pcm_lib_ioctl,
143 .hw_params = tegra_pcm_hw_params,
144 .hw_free = tegra_pcm_hw_free,
145 .trigger = snd_dmaengine_pcm_trigger,
146 .pointer = snd_dmaengine_pcm_pointer,
147 .mmap = tegra_pcm_mmap,
148};
149
150static int tegra_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
151{
152 struct snd_pcm_substream *substream = pcm->streams[stream].substream;
153 struct snd_dma_buffer *buf = &substream->dma_buffer;
154 size_t size = tegra_pcm_hardware.buffer_bytes_max;
155
156 buf->area = dma_alloc_writecombine(pcm->card->dev, size,
157 &buf->addr, GFP_KERNEL);
158 if (!buf->area)
159 return -ENOMEM;
160
161 buf->dev.type = SNDRV_DMA_TYPE_DEV;
162 buf->dev.dev = pcm->card->dev;
163 buf->private_data = NULL;
164 buf->bytes = size;
165
166 return 0;
167}
168
169static void tegra_pcm_deallocate_dma_buffer(struct snd_pcm *pcm, int stream)
170{
171 struct snd_pcm_substream *substream;
172 struct snd_dma_buffer *buf;
173
174 substream = pcm->streams[stream].substream;
175 if (!substream)
176 return;
177
178 buf = &substream->dma_buffer;
179 if (!buf->area)
180 return;
181
182 dma_free_writecombine(pcm->card->dev, buf->bytes,
183 buf->area, buf->addr);
184 buf->area = NULL;
185}
186
187static u64 tegra_dma_mask = DMA_BIT_MASK(32);
188
189static int tegra_pcm_new(struct snd_soc_pcm_runtime *rtd)
190{
191 struct snd_card *card = rtd->card->snd_card;
192 struct snd_pcm *pcm = rtd->pcm;
193 int ret = 0;
194
195 if (!card->dev->dma_mask)
196 card->dev->dma_mask = &tegra_dma_mask;
197 if (!card->dev->coherent_dma_mask)
198 card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
199
200 if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
201 ret = tegra_pcm_preallocate_dma_buffer(pcm,
202 SNDRV_PCM_STREAM_PLAYBACK);
203 if (ret)
204 goto err;
205 }
206
207 if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
208 ret = tegra_pcm_preallocate_dma_buffer(pcm,
209 SNDRV_PCM_STREAM_CAPTURE);
210 if (ret)
211 goto err_free_play;
212 }
213
214 return 0;
215
216err_free_play:
217 tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK);
218err:
219 return ret;
220}
221
222static void tegra_pcm_free(struct snd_pcm *pcm)
223{
224 tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE);
225 tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK);
226}
227
228static struct snd_soc_platform_driver tegra_pcm_platform = {
229 .ops = &tegra_pcm_ops,
230 .pcm_new = tegra_pcm_new,
231 .pcm_free = tegra_pcm_free,
232};
233
234int tegra_pcm_platform_register(struct device *dev)
235{
236 return snd_soc_register_platform(dev, &tegra_pcm_platform);
237}
238EXPORT_SYMBOL_GPL(tegra_pcm_platform_register);
239
240void tegra_pcm_platform_unregister(struct device *dev)
241{
242 snd_soc_unregister_platform(dev);
243}
244EXPORT_SYMBOL_GPL(tegra_pcm_platform_unregister);
245
246MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
247MODULE_DESCRIPTION("Tegra PCM ASoC driver");
248MODULE_LICENSE("GPL");
249