.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * tegra_pcm.c - Tegra PCM driver |
---|
3 | 4 | * |
---|
.. | .. |
---|
12 | 13 | * |
---|
13 | 14 | * Copyright (C) 2010 Google, Inc. |
---|
14 | 15 | * Iliyan Malchev <malchev@google.com> |
---|
15 | | - * |
---|
16 | | - * This program is free software; you can redistribute it and/or |
---|
17 | | - * modify it under the terms of the GNU General Public License |
---|
18 | | - * version 2 as published by the Free Software Foundation. |
---|
19 | | - * |
---|
20 | | - * This program is distributed in the hope that it will be useful, but |
---|
21 | | - * WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
22 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
---|
23 | | - * General Public License for more details. |
---|
24 | | - * |
---|
25 | | - * You should have received a copy of the GNU General Public License |
---|
26 | | - * along with this program; if not, write to the Free Software |
---|
27 | | - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA |
---|
28 | | - * 02110-1301 USA |
---|
29 | | - * |
---|
30 | 16 | */ |
---|
31 | 17 | |
---|
32 | 18 | #include <linux/module.h> |
---|
| 19 | +#include <linux/dma-mapping.h> |
---|
33 | 20 | #include <sound/core.h> |
---|
34 | 21 | #include <sound/pcm.h> |
---|
35 | 22 | #include <sound/pcm_params.h> |
---|
36 | 23 | #include <sound/soc.h> |
---|
37 | 24 | #include <sound/dmaengine_pcm.h> |
---|
38 | | - |
---|
39 | 25 | #include "tegra_pcm.h" |
---|
40 | 26 | |
---|
41 | 27 | static const struct snd_pcm_hardware tegra_pcm_hardware = { |
---|
.. | .. |
---|
81 | 67 | } |
---|
82 | 68 | EXPORT_SYMBOL_GPL(tegra_pcm_platform_unregister); |
---|
83 | 69 | |
---|
| 70 | +int tegra_pcm_open(struct snd_soc_component *component, |
---|
| 71 | + struct snd_pcm_substream *substream) |
---|
| 72 | +{ |
---|
| 73 | + struct snd_soc_pcm_runtime *rtd = substream->private_data; |
---|
| 74 | + struct snd_dmaengine_dai_dma_data *dmap; |
---|
| 75 | + struct dma_chan *chan; |
---|
| 76 | + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); |
---|
| 77 | + int ret; |
---|
| 78 | + |
---|
| 79 | + if (rtd->dai_link->no_pcm) |
---|
| 80 | + return 0; |
---|
| 81 | + |
---|
| 82 | + dmap = snd_soc_dai_get_dma_data(cpu_dai, substream); |
---|
| 83 | + |
---|
| 84 | + /* Set HW params now that initialization is complete */ |
---|
| 85 | + snd_soc_set_runtime_hwparams(substream, &tegra_pcm_hardware); |
---|
| 86 | + |
---|
| 87 | + /* Ensure period size is multiple of 8 */ |
---|
| 88 | + ret = snd_pcm_hw_constraint_step(substream->runtime, 0, |
---|
| 89 | + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 0x8); |
---|
| 90 | + if (ret) { |
---|
| 91 | + dev_err(rtd->dev, "failed to set constraint %d\n", ret); |
---|
| 92 | + return ret; |
---|
| 93 | + } |
---|
| 94 | + |
---|
| 95 | + chan = dma_request_slave_channel(cpu_dai->dev, dmap->chan_name); |
---|
| 96 | + if (!chan) { |
---|
| 97 | + dev_err(cpu_dai->dev, |
---|
| 98 | + "dmaengine request slave channel failed! (%s)\n", |
---|
| 99 | + dmap->chan_name); |
---|
| 100 | + return -ENODEV; |
---|
| 101 | + } |
---|
| 102 | + |
---|
| 103 | + ret = snd_dmaengine_pcm_open(substream, chan); |
---|
| 104 | + if (ret) { |
---|
| 105 | + dev_err(rtd->dev, |
---|
| 106 | + "dmaengine pcm open failed with err %d (%s)\n", ret, |
---|
| 107 | + dmap->chan_name); |
---|
| 108 | + |
---|
| 109 | + dma_release_channel(chan); |
---|
| 110 | + |
---|
| 111 | + return ret; |
---|
| 112 | + } |
---|
| 113 | + |
---|
| 114 | + return 0; |
---|
| 115 | +} |
---|
| 116 | +EXPORT_SYMBOL_GPL(tegra_pcm_open); |
---|
| 117 | + |
---|
| 118 | +int tegra_pcm_close(struct snd_soc_component *component, |
---|
| 119 | + struct snd_pcm_substream *substream) |
---|
| 120 | +{ |
---|
| 121 | + struct snd_soc_pcm_runtime *rtd = substream->private_data; |
---|
| 122 | + |
---|
| 123 | + if (rtd->dai_link->no_pcm) |
---|
| 124 | + return 0; |
---|
| 125 | + |
---|
| 126 | + snd_dmaengine_pcm_close_release_chan(substream); |
---|
| 127 | + |
---|
| 128 | + return 0; |
---|
| 129 | +} |
---|
| 130 | +EXPORT_SYMBOL_GPL(tegra_pcm_close); |
---|
| 131 | + |
---|
| 132 | +int tegra_pcm_hw_params(struct snd_soc_component *component, |
---|
| 133 | + struct snd_pcm_substream *substream, |
---|
| 134 | + struct snd_pcm_hw_params *params) |
---|
| 135 | +{ |
---|
| 136 | + struct snd_soc_pcm_runtime *rtd = substream->private_data; |
---|
| 137 | + struct snd_dmaengine_dai_dma_data *dmap; |
---|
| 138 | + struct dma_slave_config slave_config; |
---|
| 139 | + struct dma_chan *chan; |
---|
| 140 | + int ret; |
---|
| 141 | + |
---|
| 142 | + if (rtd->dai_link->no_pcm) |
---|
| 143 | + return 0; |
---|
| 144 | + |
---|
| 145 | + dmap = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); |
---|
| 146 | + if (!dmap) |
---|
| 147 | + return 0; |
---|
| 148 | + |
---|
| 149 | + chan = snd_dmaengine_pcm_get_chan(substream); |
---|
| 150 | + |
---|
| 151 | + ret = snd_hwparams_to_dma_slave_config(substream, params, |
---|
| 152 | + &slave_config); |
---|
| 153 | + if (ret) { |
---|
| 154 | + dev_err(rtd->dev, "hw params config failed with err %d\n", ret); |
---|
| 155 | + return ret; |
---|
| 156 | + } |
---|
| 157 | + |
---|
| 158 | + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
---|
| 159 | + slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
---|
| 160 | + slave_config.dst_addr = dmap->addr; |
---|
| 161 | + slave_config.dst_maxburst = 8; |
---|
| 162 | + } else { |
---|
| 163 | + slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
---|
| 164 | + slave_config.src_addr = dmap->addr; |
---|
| 165 | + slave_config.src_maxburst = 8; |
---|
| 166 | + } |
---|
| 167 | + |
---|
| 168 | + ret = dmaengine_slave_config(chan, &slave_config); |
---|
| 169 | + if (ret < 0) { |
---|
| 170 | + dev_err(rtd->dev, "dma slave config failed with err %d\n", ret); |
---|
| 171 | + return ret; |
---|
| 172 | + } |
---|
| 173 | + |
---|
| 174 | + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); |
---|
| 175 | + |
---|
| 176 | + return 0; |
---|
| 177 | +} |
---|
| 178 | +EXPORT_SYMBOL_GPL(tegra_pcm_hw_params); |
---|
| 179 | + |
---|
| 180 | +int tegra_pcm_hw_free(struct snd_soc_component *component, |
---|
| 181 | + struct snd_pcm_substream *substream) |
---|
| 182 | +{ |
---|
| 183 | + struct snd_soc_pcm_runtime *rtd = substream->private_data; |
---|
| 184 | + |
---|
| 185 | + if (rtd->dai_link->no_pcm) |
---|
| 186 | + return 0; |
---|
| 187 | + |
---|
| 188 | + snd_pcm_set_runtime_buffer(substream, NULL); |
---|
| 189 | + |
---|
| 190 | + return 0; |
---|
| 191 | +} |
---|
| 192 | +EXPORT_SYMBOL_GPL(tegra_pcm_hw_free); |
---|
| 193 | + |
---|
| 194 | +int tegra_pcm_mmap(struct snd_soc_component *component, |
---|
| 195 | + struct snd_pcm_substream *substream, |
---|
| 196 | + struct vm_area_struct *vma) |
---|
| 197 | +{ |
---|
| 198 | + struct snd_soc_pcm_runtime *rtd = substream->private_data; |
---|
| 199 | + struct snd_pcm_runtime *runtime = substream->runtime; |
---|
| 200 | + |
---|
| 201 | + if (rtd->dai_link->no_pcm) |
---|
| 202 | + return 0; |
---|
| 203 | + |
---|
| 204 | + return dma_mmap_wc(substream->pcm->card->dev, vma, runtime->dma_area, |
---|
| 205 | + runtime->dma_addr, runtime->dma_bytes); |
---|
| 206 | +} |
---|
| 207 | +EXPORT_SYMBOL_GPL(tegra_pcm_mmap); |
---|
| 208 | + |
---|
| 209 | +snd_pcm_uframes_t tegra_pcm_pointer(struct snd_soc_component *component, |
---|
| 210 | + struct snd_pcm_substream *substream) |
---|
| 211 | +{ |
---|
| 212 | + return snd_dmaengine_pcm_pointer(substream); |
---|
| 213 | +} |
---|
| 214 | +EXPORT_SYMBOL_GPL(tegra_pcm_pointer); |
---|
| 215 | + |
---|
| 216 | +static int tegra_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream, |
---|
| 217 | + size_t size) |
---|
| 218 | +{ |
---|
| 219 | + struct snd_pcm_substream *substream = pcm->streams[stream].substream; |
---|
| 220 | + struct snd_dma_buffer *buf = &substream->dma_buffer; |
---|
| 221 | + |
---|
| 222 | + buf->area = dma_alloc_wc(pcm->card->dev, size, &buf->addr, GFP_KERNEL); |
---|
| 223 | + if (!buf->area) |
---|
| 224 | + return -ENOMEM; |
---|
| 225 | + |
---|
| 226 | + buf->private_data = NULL; |
---|
| 227 | + buf->dev.type = SNDRV_DMA_TYPE_DEV; |
---|
| 228 | + buf->dev.dev = pcm->card->dev; |
---|
| 229 | + buf->bytes = size; |
---|
| 230 | + |
---|
| 231 | + return 0; |
---|
| 232 | +} |
---|
| 233 | + |
---|
| 234 | +static void tegra_pcm_deallocate_dma_buffer(struct snd_pcm *pcm, int stream) |
---|
| 235 | +{ |
---|
| 236 | + struct snd_pcm_substream *substream; |
---|
| 237 | + struct snd_dma_buffer *buf; |
---|
| 238 | + |
---|
| 239 | + substream = pcm->streams[stream].substream; |
---|
| 240 | + if (!substream) |
---|
| 241 | + return; |
---|
| 242 | + |
---|
| 243 | + buf = &substream->dma_buffer; |
---|
| 244 | + if (!buf->area) |
---|
| 245 | + return; |
---|
| 246 | + |
---|
| 247 | + dma_free_wc(pcm->card->dev, buf->bytes, buf->area, buf->addr); |
---|
| 248 | + buf->area = NULL; |
---|
| 249 | +} |
---|
| 250 | + |
---|
| 251 | +static int tegra_pcm_dma_allocate(struct snd_soc_pcm_runtime *rtd, |
---|
| 252 | + size_t size) |
---|
| 253 | +{ |
---|
| 254 | + struct snd_card *card = rtd->card->snd_card; |
---|
| 255 | + struct snd_pcm *pcm = rtd->pcm; |
---|
| 256 | + int ret; |
---|
| 257 | + |
---|
| 258 | + ret = dma_set_mask(card->dev, DMA_BIT_MASK(32)); |
---|
| 259 | + if (ret < 0) |
---|
| 260 | + return ret; |
---|
| 261 | + |
---|
| 262 | + ret = dma_set_coherent_mask(card->dev, DMA_BIT_MASK(32)); |
---|
| 263 | + if (ret < 0) |
---|
| 264 | + return ret; |
---|
| 265 | + |
---|
| 266 | + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { |
---|
| 267 | + ret = tegra_pcm_preallocate_dma_buffer(pcm, |
---|
| 268 | + SNDRV_PCM_STREAM_PLAYBACK, size); |
---|
| 269 | + if (ret) |
---|
| 270 | + goto err; |
---|
| 271 | + } |
---|
| 272 | + |
---|
| 273 | + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { |
---|
| 274 | + ret = tegra_pcm_preallocate_dma_buffer(pcm, |
---|
| 275 | + SNDRV_PCM_STREAM_CAPTURE, size); |
---|
| 276 | + if (ret) |
---|
| 277 | + goto err_free_play; |
---|
| 278 | + } |
---|
| 279 | + |
---|
| 280 | + return 0; |
---|
| 281 | + |
---|
| 282 | +err_free_play: |
---|
| 283 | + tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); |
---|
| 284 | +err: |
---|
| 285 | + return ret; |
---|
| 286 | +} |
---|
| 287 | + |
---|
| 288 | +int tegra_pcm_construct(struct snd_soc_component *component, |
---|
| 289 | + struct snd_soc_pcm_runtime *rtd) |
---|
| 290 | +{ |
---|
| 291 | + return tegra_pcm_dma_allocate(rtd, tegra_pcm_hardware.buffer_bytes_max); |
---|
| 292 | +} |
---|
| 293 | +EXPORT_SYMBOL_GPL(tegra_pcm_construct); |
---|
| 294 | + |
---|
| 295 | +void tegra_pcm_destruct(struct snd_soc_component *component, |
---|
| 296 | + struct snd_pcm *pcm) |
---|
| 297 | +{ |
---|
| 298 | + tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE); |
---|
| 299 | + tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); |
---|
| 300 | +} |
---|
| 301 | +EXPORT_SYMBOL_GPL(tegra_pcm_destruct); |
---|
| 302 | + |
---|
84 | 303 | MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); |
---|
85 | 304 | MODULE_DESCRIPTION("Tegra PCM ASoC driver"); |
---|
86 | 305 | MODULE_LICENSE("GPL"); |
---|