linux/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/* Copyright 2011 Broadcom Corporation.  All rights reserved. */
   3
   4#include <linux/interrupt.h>
   5#include <linux/slab.h>
   6
   7#include <sound/asoundef.h>
   8
   9#include "bcm2835.h"
  10
  11/* hardware definition */
  12static const struct snd_pcm_hardware snd_bcm2835_playback_hw = {
  13        .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
  14                 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
  15                 SNDRV_PCM_INFO_SYNC_APPLPTR | SNDRV_PCM_INFO_BATCH),
  16        .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
  17        .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_192000,
  18        .rate_min = 8000,
  19        .rate_max = 192000,
  20        .channels_min = 1,
  21        .channels_max = 8,
  22        .buffer_bytes_max = 512 * 1024,
  23        .period_bytes_min = 1 * 1024,
  24        .period_bytes_max = 512 * 1024,
  25        .periods_min = 1,
  26        .periods_max = 128,
  27};
  28
  29static const struct snd_pcm_hardware snd_bcm2835_playback_spdif_hw = {
  30        .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
  31                 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
  32                 SNDRV_PCM_INFO_SYNC_APPLPTR | SNDRV_PCM_INFO_BATCH),
  33        .formats = SNDRV_PCM_FMTBIT_S16_LE,
  34        .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_44100 |
  35        SNDRV_PCM_RATE_48000,
  36        .rate_min = 44100,
  37        .rate_max = 48000,
  38        .channels_min = 2,
  39        .channels_max = 2,
  40        .buffer_bytes_max = 128 * 1024,
  41        .period_bytes_min = 1 * 1024,
  42        .period_bytes_max = 128 * 1024,
  43        .periods_min = 1,
  44        .periods_max = 128,
  45};
  46
  47static void snd_bcm2835_playback_free(struct snd_pcm_runtime *runtime)
  48{
  49        kfree(runtime->private_data);
  50}
  51
  52void bcm2835_playback_fifo(struct bcm2835_alsa_stream *alsa_stream,
  53                           unsigned int bytes)
  54{
  55        struct snd_pcm_substream *substream = alsa_stream->substream;
  56        unsigned int pos;
  57
  58        if (!alsa_stream->period_size)
  59                return;
  60
  61        if (bytes >= alsa_stream->buffer_size) {
  62                snd_pcm_stream_lock(substream);
  63                snd_pcm_stop(substream,
  64                             alsa_stream->draining ?
  65                             SNDRV_PCM_STATE_SETUP :
  66                             SNDRV_PCM_STATE_XRUN);
  67                snd_pcm_stream_unlock(substream);
  68                return;
  69        }
  70
  71        pos = atomic_read(&alsa_stream->pos);
  72        pos += bytes;
  73        pos %= alsa_stream->buffer_size;
  74        atomic_set(&alsa_stream->pos, pos);
  75
  76        alsa_stream->period_offset += bytes;
  77        alsa_stream->interpolate_start = ktime_get();
  78        if (alsa_stream->period_offset >= alsa_stream->period_size) {
  79                alsa_stream->period_offset %= alsa_stream->period_size;
  80                snd_pcm_period_elapsed(substream);
  81        }
  82}
  83
  84/* open callback */
  85static int snd_bcm2835_playback_open_generic(
  86        struct snd_pcm_substream *substream, int spdif)
  87{
  88        struct bcm2835_chip *chip = snd_pcm_substream_chip(substream);
  89        struct snd_pcm_runtime *runtime = substream->runtime;
  90        struct bcm2835_alsa_stream *alsa_stream;
  91        int idx;
  92        int err;
  93
  94        mutex_lock(&chip->audio_mutex);
  95        idx = substream->number;
  96
  97        if (spdif && chip->opened) {
  98                err = -EBUSY;
  99                goto out;
 100        } else if (!spdif && (chip->opened & (1 << idx))) {
 101                err = -EBUSY;
 102                goto out;
 103        }
 104        if (idx >= MAX_SUBSTREAMS) {
 105                dev_err(chip->dev,
 106                        "substream(%d) device doesn't exist max(%d) substreams allowed\n",
 107                        idx, MAX_SUBSTREAMS);
 108                err = -ENODEV;
 109                goto out;
 110        }
 111
 112        alsa_stream = kzalloc(sizeof(*alsa_stream), GFP_KERNEL);
 113        if (!alsa_stream) {
 114                err = -ENOMEM;
 115                goto out;
 116        }
 117
 118        /* Initialise alsa_stream */
 119        alsa_stream->chip = chip;
 120        alsa_stream->substream = substream;
 121        alsa_stream->idx = idx;
 122
 123        err = bcm2835_audio_open(alsa_stream);
 124        if (err) {
 125                kfree(alsa_stream);
 126                goto out;
 127        }
 128        runtime->private_data = alsa_stream;
 129        runtime->private_free = snd_bcm2835_playback_free;
 130        if (spdif) {
 131                runtime->hw = snd_bcm2835_playback_spdif_hw;
 132        } else {
 133                /* clear spdif status, as we are not in spdif mode */
 134                chip->spdif_status = 0;
 135                runtime->hw = snd_bcm2835_playback_hw;
 136        }
 137        /* minimum 16 bytes alignment (for vchiq bulk transfers) */
 138        snd_pcm_hw_constraint_step(runtime,
 139                                   0,
 140                                   SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
 141                                   16);
 142
 143        /* position update is in 10ms order */
 144        snd_pcm_hw_constraint_minmax(runtime,
 145                                     SNDRV_PCM_HW_PARAM_PERIOD_TIME,
 146                                     10 * 1000, UINT_MAX);
 147
 148        chip->alsa_stream[idx] = alsa_stream;
 149
 150        chip->opened |= (1 << idx);
 151
 152out:
 153        mutex_unlock(&chip->audio_mutex);
 154
 155        return err;
 156}
 157
 158static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)
 159{
 160        return snd_bcm2835_playback_open_generic(substream, 0);
 161}
 162
 163static int snd_bcm2835_playback_spdif_open(struct snd_pcm_substream *substream)
 164{
 165        return snd_bcm2835_playback_open_generic(substream, 1);
 166}
 167
 168static int snd_bcm2835_playback_close(struct snd_pcm_substream *substream)
 169{
 170        struct bcm2835_alsa_stream *alsa_stream;
 171        struct snd_pcm_runtime *runtime;
 172        struct bcm2835_chip *chip;
 173
 174        chip = snd_pcm_substream_chip(substream);
 175        mutex_lock(&chip->audio_mutex);
 176        runtime = substream->runtime;
 177        alsa_stream = runtime->private_data;
 178
 179        alsa_stream->period_size = 0;
 180        alsa_stream->buffer_size = 0;
 181
 182        bcm2835_audio_close(alsa_stream);
 183        alsa_stream->chip->alsa_stream[alsa_stream->idx] = NULL;
 184        /*
 185         * Do not free up alsa_stream here, it will be freed up by
 186         * runtime->private_free callback we registered in *_open above
 187         */
 188
 189        chip->opened &= ~(1 << substream->number);
 190
 191        mutex_unlock(&chip->audio_mutex);
 192
 193        return 0;
 194}
 195
 196static int snd_bcm2835_pcm_prepare(struct snd_pcm_substream *substream)
 197{
 198        struct bcm2835_chip *chip = snd_pcm_substream_chip(substream);
 199        struct snd_pcm_runtime *runtime = substream->runtime;
 200        struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
 201        int channels;
 202        int err;
 203
 204        /* notify the vchiq that it should enter spdif passthrough mode by
 205         * setting channels=0 (see
 206         * https://github.com/raspberrypi/linux/issues/528)
 207         */
 208        if (chip->spdif_status & IEC958_AES0_NONAUDIO)
 209                channels = 0;
 210        else
 211                channels = runtime->channels;
 212
 213        err = bcm2835_audio_set_params(alsa_stream, channels,
 214                                       runtime->rate,
 215                                       snd_pcm_format_width(runtime->format));
 216        if (err < 0)
 217                return err;
 218
 219        memset(&alsa_stream->pcm_indirect, 0, sizeof(alsa_stream->pcm_indirect));
 220
 221        alsa_stream->pcm_indirect.hw_buffer_size =
 222                alsa_stream->pcm_indirect.sw_buffer_size =
 223                snd_pcm_lib_buffer_bytes(substream);
 224
 225        alsa_stream->buffer_size = snd_pcm_lib_buffer_bytes(substream);
 226        alsa_stream->period_size = snd_pcm_lib_period_bytes(substream);
 227        atomic_set(&alsa_stream->pos, 0);
 228        alsa_stream->period_offset = 0;
 229        alsa_stream->draining = false;
 230        alsa_stream->interpolate_start = ktime_get();
 231
 232        return 0;
 233}
 234
 235static void snd_bcm2835_pcm_transfer(struct snd_pcm_substream *substream,
 236                                     struct snd_pcm_indirect *rec, size_t bytes)
 237{
 238        struct snd_pcm_runtime *runtime = substream->runtime;
 239        struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
 240        void *src = (void *) (substream->runtime->dma_area + rec->sw_data);
 241
 242        bcm2835_audio_write(alsa_stream, bytes, src);
 243}
 244
 245static int snd_bcm2835_pcm_ack(struct snd_pcm_substream *substream)
 246{
 247        struct snd_pcm_runtime *runtime = substream->runtime;
 248        struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
 249        struct snd_pcm_indirect *pcm_indirect = &alsa_stream->pcm_indirect;
 250
 251        return snd_pcm_indirect_playback_transfer(substream, pcm_indirect,
 252                                                  snd_bcm2835_pcm_transfer);
 253}
 254
 255/* trigger callback */
 256static int snd_bcm2835_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 257{
 258        struct snd_pcm_runtime *runtime = substream->runtime;
 259        struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
 260
 261        switch (cmd) {
 262        case SNDRV_PCM_TRIGGER_START:
 263                return bcm2835_audio_start(alsa_stream);
 264        case SNDRV_PCM_TRIGGER_DRAIN:
 265                alsa_stream->draining = true;
 266                return bcm2835_audio_drain(alsa_stream);
 267        case SNDRV_PCM_TRIGGER_STOP:
 268                return bcm2835_audio_stop(alsa_stream);
 269        default:
 270                return -EINVAL;
 271        }
 272}
 273
 274/* pointer callback */
 275static snd_pcm_uframes_t
 276snd_bcm2835_pcm_pointer(struct snd_pcm_substream *substream)
 277{
 278        struct snd_pcm_runtime *runtime = substream->runtime;
 279        struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
 280        ktime_t now = ktime_get();
 281
 282        /* Give userspace better delay reporting by interpolating between GPU
 283         * notifications, assuming audio speed is close enough to the clock
 284         * used for ktime
 285         */
 286
 287        if ((ktime_to_ns(alsa_stream->interpolate_start)) &&
 288            (ktime_compare(alsa_stream->interpolate_start, now) < 0)) {
 289                u64 interval =
 290                        (ktime_to_ns(ktime_sub(now,
 291                                alsa_stream->interpolate_start)));
 292                u64 frames_output_in_interval =
 293                        div_u64((interval * runtime->rate), 1000000000);
 294                snd_pcm_sframes_t frames_output_in_interval_sized =
 295                        -frames_output_in_interval;
 296                runtime->delay = frames_output_in_interval_sized;
 297        }
 298
 299        return snd_pcm_indirect_playback_pointer(substream,
 300                &alsa_stream->pcm_indirect,
 301                atomic_read(&alsa_stream->pos));
 302}
 303
 304/* operators */
 305static const struct snd_pcm_ops snd_bcm2835_playback_ops = {
 306        .open = snd_bcm2835_playback_open,
 307        .close = snd_bcm2835_playback_close,
 308        .prepare = snd_bcm2835_pcm_prepare,
 309        .trigger = snd_bcm2835_pcm_trigger,
 310        .pointer = snd_bcm2835_pcm_pointer,
 311        .ack = snd_bcm2835_pcm_ack,
 312};
 313
 314static const struct snd_pcm_ops snd_bcm2835_playback_spdif_ops = {
 315        .open = snd_bcm2835_playback_spdif_open,
 316        .close = snd_bcm2835_playback_close,
 317        .prepare = snd_bcm2835_pcm_prepare,
 318        .trigger = snd_bcm2835_pcm_trigger,
 319        .pointer = snd_bcm2835_pcm_pointer,
 320        .ack = snd_bcm2835_pcm_ack,
 321};
 322
 323/* create a pcm device */
 324int snd_bcm2835_new_pcm(struct bcm2835_chip *chip, const char *name,
 325                        int idx, enum snd_bcm2835_route route,
 326                        u32 numchannels, bool spdif)
 327{
 328        struct snd_pcm *pcm;
 329        int err;
 330
 331        err = snd_pcm_new(chip->card, name, idx, numchannels, 0, &pcm);
 332        if (err)
 333                return err;
 334
 335        pcm->private_data = chip;
 336        pcm->nonatomic = true;
 337        strscpy(pcm->name, name, sizeof(pcm->name));
 338        if (!spdif) {
 339                chip->dest = route;
 340                chip->volume = 0;
 341                chip->mute = CTRL_VOL_UNMUTE;
 342        }
 343
 344        snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
 345                        spdif ? &snd_bcm2835_playback_spdif_ops :
 346                        &snd_bcm2835_playback_ops);
 347
 348        snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
 349                                       chip->card->dev, 128 * 1024, 128 * 1024);
 350
 351        if (spdif)
 352                chip->pcm_spdif = pcm;
 353        else
 354                chip->pcm = pcm;
 355        return 0;
 356}
 357