| .. | .. |
|---|
| 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"); |
|---|