linux/drivers/media/pci/cobalt/cobalt-alsa-pcm.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 *  ALSA PCM device for the
   4 *  ALSA interface to cobalt PCM capture streams
   5 *
   6 *  Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates.
   7 *  All rights reserved.
   8 */
   9
  10#include <linux/init.h>
  11#include <linux/kernel.h>
  12#include <linux/vmalloc.h>
  13#include <linux/delay.h>
  14
  15#include <media/v4l2-device.h>
  16
  17#include <sound/core.h>
  18#include <sound/pcm.h>
  19
  20#include "cobalt-driver.h"
  21#include "cobalt-alsa.h"
  22#include "cobalt-alsa-pcm.h"
  23
  24static unsigned int pcm_debug;
  25module_param(pcm_debug, int, 0644);
  26MODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm");
  27
  28#define dprintk(fmt, arg...) \
  29        do { \
  30                if (pcm_debug) \
  31                        pr_info("cobalt-alsa-pcm %s: " fmt, __func__, ##arg); \
  32        } while (0)
  33
  34static const struct snd_pcm_hardware snd_cobalt_hdmi_capture = {
  35        .info = SNDRV_PCM_INFO_BLOCK_TRANSFER |
  36                SNDRV_PCM_INFO_MMAP           |
  37                SNDRV_PCM_INFO_INTERLEAVED    |
  38                SNDRV_PCM_INFO_MMAP_VALID,
  39
  40        .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
  41
  42        .rates = SNDRV_PCM_RATE_48000,
  43
  44        .rate_min = 48000,
  45        .rate_max = 48000,
  46        .channels_min = 1,
  47        .channels_max = 8,
  48        .buffer_bytes_max = 4 * 240 * 8 * 4,    /* 5 ms of data */
  49        .period_bytes_min = 1920,               /* 1 sample = 8 * 4 bytes */
  50        .period_bytes_max = 240 * 8 * 4,        /* 5 ms of 8 channel data */
  51        .periods_min = 1,
  52        .periods_max = 4,
  53};
  54
  55static const struct snd_pcm_hardware snd_cobalt_playback = {
  56        .info = SNDRV_PCM_INFO_BLOCK_TRANSFER |
  57                SNDRV_PCM_INFO_MMAP           |
  58                SNDRV_PCM_INFO_INTERLEAVED    |
  59                SNDRV_PCM_INFO_MMAP_VALID,
  60
  61        .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE,
  62
  63        .rates = SNDRV_PCM_RATE_48000,
  64
  65        .rate_min = 48000,
  66        .rate_max = 48000,
  67        .channels_min = 1,
  68        .channels_max = 8,
  69        .buffer_bytes_max = 4 * 240 * 8 * 4,    /* 5 ms of data */
  70        .period_bytes_min = 1920,               /* 1 sample = 8 * 4 bytes */
  71        .period_bytes_max = 240 * 8 * 4,        /* 5 ms of 8 channel data */
  72        .periods_min = 1,
  73        .periods_max = 4,
  74};
  75
  76static void sample_cpy(u8 *dst, const u8 *src, u32 len, bool is_s32)
  77{
  78        static const unsigned map[8] = { 0, 1, 5, 4, 2, 3, 6, 7 };
  79        unsigned idx = 0;
  80
  81        while (len >= (is_s32 ? 4 : 2)) {
  82                unsigned offset = map[idx] * 4;
  83                u32 val = src[offset + 1] + (src[offset + 2] << 8) +
  84                         (src[offset + 3] << 16);
  85
  86                if (is_s32) {
  87                        *dst++ = 0;
  88                        *dst++ = val & 0xff;
  89                }
  90                *dst++ = (val >> 8) & 0xff;
  91                *dst++ = (val >> 16) & 0xff;
  92                len -= is_s32 ? 4 : 2;
  93                idx++;
  94        }
  95}
  96
  97static void cobalt_alsa_announce_pcm_data(struct snd_cobalt_card *cobsc,
  98                                        u8 *pcm_data,
  99                                        size_t skip,
 100                                        size_t samples)
 101{
 102        struct snd_pcm_substream *substream;
 103        struct snd_pcm_runtime *runtime;
 104        unsigned long flags;
 105        unsigned int oldptr;
 106        unsigned int stride;
 107        int length = samples;
 108        int period_elapsed = 0;
 109        bool is_s32;
 110
 111        dprintk("cobalt alsa announce ptr=%p data=%p num_bytes=%zd\n", cobsc,
 112                pcm_data, samples);
 113
 114        substream = cobsc->capture_pcm_substream;
 115        if (substream == NULL) {
 116                dprintk("substream was NULL\n");
 117                return;
 118        }
 119
 120        runtime = substream->runtime;
 121        if (runtime == NULL) {
 122                dprintk("runtime was NULL\n");
 123                return;
 124        }
 125        is_s32 = runtime->format == SNDRV_PCM_FORMAT_S32_LE;
 126
 127        stride = runtime->frame_bits >> 3;
 128        if (stride == 0) {
 129                dprintk("stride is zero\n");
 130                return;
 131        }
 132
 133        if (length == 0) {
 134                dprintk("%s: length was zero\n", __func__);
 135                return;
 136        }
 137
 138        if (runtime->dma_area == NULL) {
 139                dprintk("dma area was NULL - ignoring\n");
 140                return;
 141        }
 142
 143        oldptr = cobsc->hwptr_done_capture;
 144        if (oldptr + length >= runtime->buffer_size) {
 145                unsigned int cnt = runtime->buffer_size - oldptr;
 146                unsigned i;
 147
 148                for (i = 0; i < cnt; i++)
 149                        sample_cpy(runtime->dma_area + (oldptr + i) * stride,
 150                                        pcm_data + i * skip,
 151                                        stride, is_s32);
 152                for (i = cnt; i < length; i++)
 153                        sample_cpy(runtime->dma_area + (i - cnt) * stride,
 154                                        pcm_data + i * skip, stride, is_s32);
 155        } else {
 156                unsigned i;
 157
 158                for (i = 0; i < length; i++)
 159                        sample_cpy(runtime->dma_area + (oldptr + i) * stride,
 160                                        pcm_data + i * skip,
 161                                        stride, is_s32);
 162        }
 163        snd_pcm_stream_lock_irqsave(substream, flags);
 164
 165        cobsc->hwptr_done_capture += length;
 166        if (cobsc->hwptr_done_capture >=
 167            runtime->buffer_size)
 168                cobsc->hwptr_done_capture -=
 169                        runtime->buffer_size;
 170
 171        cobsc->capture_transfer_done += length;
 172        if (cobsc->capture_transfer_done >=
 173            runtime->period_size) {
 174                cobsc->capture_transfer_done -=
 175                        runtime->period_size;
 176                period_elapsed = 1;
 177        }
 178
 179        snd_pcm_stream_unlock_irqrestore(substream, flags);
 180
 181        if (period_elapsed)
 182                snd_pcm_period_elapsed(substream);
 183}
 184
 185static int alsa_fnc(struct vb2_buffer *vb, void *priv)
 186{
 187        struct cobalt_stream *s = priv;
 188        unsigned char *p = vb2_plane_vaddr(vb, 0);
 189        int i;
 190
 191        if (pcm_debug) {
 192                pr_info("alsa: ");
 193                for (i = 0; i < 8 * 4; i++) {
 194                        if (!(i & 3))
 195                                pr_cont(" ");
 196                        pr_cont("%02x", p[i]);
 197                }
 198                pr_cont("\n");
 199        }
 200        cobalt_alsa_announce_pcm_data(s->alsa,
 201                        vb2_plane_vaddr(vb, 0),
 202                        8 * 4,
 203                        vb2_get_plane_payload(vb, 0) / (8 * 4));
 204        return 0;
 205}
 206
 207static int snd_cobalt_pcm_capture_open(struct snd_pcm_substream *substream)
 208{
 209        struct snd_pcm_runtime *runtime = substream->runtime;
 210        struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
 211        struct cobalt_stream *s = cobsc->s;
 212
 213        runtime->hw = snd_cobalt_hdmi_capture;
 214        snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
 215        cobsc->capture_pcm_substream = substream;
 216        runtime->private_data = s;
 217        cobsc->alsa_record_cnt++;
 218        if (cobsc->alsa_record_cnt == 1) {
 219                int rc;
 220
 221                rc = vb2_thread_start(&s->q, alsa_fnc, s, s->vdev.name);
 222                if (rc) {
 223                        cobsc->alsa_record_cnt--;
 224                        return rc;
 225                }
 226        }
 227        return 0;
 228}
 229
 230static int snd_cobalt_pcm_capture_close(struct snd_pcm_substream *substream)
 231{
 232        struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
 233        struct cobalt_stream *s = cobsc->s;
 234
 235        cobsc->alsa_record_cnt--;
 236        if (cobsc->alsa_record_cnt == 0)
 237                vb2_thread_stop(&s->q);
 238        return 0;
 239}
 240
 241static int snd_cobalt_pcm_ioctl(struct snd_pcm_substream *substream,
 242                     unsigned int cmd, void *arg)
 243{
 244        return snd_pcm_lib_ioctl(substream, cmd, arg);
 245}
 246
 247
 248static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs,
 249                                        size_t size)
 250{
 251        struct snd_pcm_runtime *runtime = subs->runtime;
 252
 253        dprintk("Allocating vbuffer\n");
 254        if (runtime->dma_area) {
 255                if (runtime->dma_bytes > size)
 256                        return 0;
 257
 258                vfree(runtime->dma_area);
 259        }
 260        runtime->dma_area = vmalloc(size);
 261        if (!runtime->dma_area)
 262                return -ENOMEM;
 263
 264        runtime->dma_bytes = size;
 265
 266        return 0;
 267}
 268
 269static int snd_cobalt_pcm_hw_params(struct snd_pcm_substream *substream,
 270                         struct snd_pcm_hw_params *params)
 271{
 272        dprintk("%s called\n", __func__);
 273
 274        return snd_pcm_alloc_vmalloc_buffer(substream,
 275                                           params_buffer_bytes(params));
 276}
 277
 278static int snd_cobalt_pcm_hw_free(struct snd_pcm_substream *substream)
 279{
 280        if (substream->runtime->dma_area) {
 281                dprintk("freeing pcm capture region\n");
 282                vfree(substream->runtime->dma_area);
 283                substream->runtime->dma_area = NULL;
 284        }
 285
 286        return 0;
 287}
 288
 289static int snd_cobalt_pcm_prepare(struct snd_pcm_substream *substream)
 290{
 291        struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
 292
 293        cobsc->hwptr_done_capture = 0;
 294        cobsc->capture_transfer_done = 0;
 295
 296        return 0;
 297}
 298
 299static int snd_cobalt_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 300{
 301        switch (cmd) {
 302        case SNDRV_PCM_TRIGGER_START:
 303        case SNDRV_PCM_TRIGGER_STOP:
 304                return 0;
 305        default:
 306                return -EINVAL;
 307        }
 308        return 0;
 309}
 310
 311static
 312snd_pcm_uframes_t snd_cobalt_pcm_pointer(struct snd_pcm_substream *substream)
 313{
 314        snd_pcm_uframes_t hwptr_done;
 315        struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
 316
 317        hwptr_done = cobsc->hwptr_done_capture;
 318
 319        return hwptr_done;
 320}
 321
 322static void pb_sample_cpy(u8 *dst, const u8 *src, u32 len, bool is_s32)
 323{
 324        static const unsigned map[8] = { 0, 1, 5, 4, 2, 3, 6, 7 };
 325        unsigned idx = 0;
 326
 327        while (len >= (is_s32 ? 4 : 2)) {
 328                unsigned offset = map[idx] * 4;
 329                u8 *out = dst + offset;
 330
 331                *out++ = 0;
 332                if (is_s32) {
 333                        src++;
 334                        *out++ = *src++;
 335                } else {
 336                        *out++ = 0;
 337                }
 338                *out++ = *src++;
 339                *out = *src++;
 340                len -= is_s32 ? 4 : 2;
 341                idx++;
 342        }
 343}
 344
 345static void cobalt_alsa_pb_pcm_data(struct snd_cobalt_card *cobsc,
 346                                        u8 *pcm_data,
 347                                        size_t skip,
 348                                        size_t samples)
 349{
 350        struct snd_pcm_substream *substream;
 351        struct snd_pcm_runtime *runtime;
 352        unsigned long flags;
 353        unsigned int pos;
 354        unsigned int stride;
 355        bool is_s32;
 356        unsigned i;
 357
 358        dprintk("cobalt alsa pb ptr=%p data=%p samples=%zd\n", cobsc,
 359                pcm_data, samples);
 360
 361        substream = cobsc->playback_pcm_substream;
 362        if (substream == NULL) {
 363                dprintk("substream was NULL\n");
 364                return;
 365        }
 366
 367        runtime = substream->runtime;
 368        if (runtime == NULL) {
 369                dprintk("runtime was NULL\n");
 370                return;
 371        }
 372
 373        is_s32 = runtime->format == SNDRV_PCM_FORMAT_S32_LE;
 374        stride = runtime->frame_bits >> 3;
 375        if (stride == 0) {
 376                dprintk("stride is zero\n");
 377                return;
 378        }
 379
 380        if (samples == 0) {
 381                dprintk("%s: samples was zero\n", __func__);
 382                return;
 383        }
 384
 385        if (runtime->dma_area == NULL) {
 386                dprintk("dma area was NULL - ignoring\n");
 387                return;
 388        }
 389
 390        pos = cobsc->pb_pos % cobsc->pb_size;
 391        for (i = 0; i < cobsc->pb_count / (8 * 4); i++)
 392                pb_sample_cpy(pcm_data + i * skip,
 393                                runtime->dma_area + pos + i * stride,
 394                                stride, is_s32);
 395        snd_pcm_stream_lock_irqsave(substream, flags);
 396
 397        cobsc->pb_pos += i * stride;
 398
 399        snd_pcm_stream_unlock_irqrestore(substream, flags);
 400        if (cobsc->pb_pos % cobsc->pb_count == 0)
 401                snd_pcm_period_elapsed(substream);
 402}
 403
 404static int alsa_pb_fnc(struct vb2_buffer *vb, void *priv)
 405{
 406        struct cobalt_stream *s = priv;
 407
 408        if (s->alsa->alsa_pb_channel)
 409                cobalt_alsa_pb_pcm_data(s->alsa,
 410                                vb2_plane_vaddr(vb, 0),
 411                                8 * 4,
 412                                vb2_get_plane_payload(vb, 0) / (8 * 4));
 413        return 0;
 414}
 415
 416static int snd_cobalt_pcm_playback_open(struct snd_pcm_substream *substream)
 417{
 418        struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
 419        struct snd_pcm_runtime *runtime = substream->runtime;
 420        struct cobalt_stream *s = cobsc->s;
 421
 422        runtime->hw = snd_cobalt_playback;
 423        snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
 424        cobsc->playback_pcm_substream = substream;
 425        runtime->private_data = s;
 426        cobsc->alsa_playback_cnt++;
 427        if (cobsc->alsa_playback_cnt == 1) {
 428                int rc;
 429
 430                rc = vb2_thread_start(&s->q, alsa_pb_fnc, s, s->vdev.name);
 431                if (rc) {
 432                        cobsc->alsa_playback_cnt--;
 433                        return rc;
 434                }
 435        }
 436
 437        return 0;
 438}
 439
 440static int snd_cobalt_pcm_playback_close(struct snd_pcm_substream *substream)
 441{
 442        struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
 443        struct cobalt_stream *s = cobsc->s;
 444
 445        cobsc->alsa_playback_cnt--;
 446        if (cobsc->alsa_playback_cnt == 0)
 447                vb2_thread_stop(&s->q);
 448        return 0;
 449}
 450
 451static int snd_cobalt_pcm_pb_prepare(struct snd_pcm_substream *substream)
 452{
 453        struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
 454
 455        cobsc->pb_size = snd_pcm_lib_buffer_bytes(substream);
 456        cobsc->pb_count = snd_pcm_lib_period_bytes(substream);
 457        cobsc->pb_pos = 0;
 458
 459        return 0;
 460}
 461
 462static int snd_cobalt_pcm_pb_trigger(struct snd_pcm_substream *substream,
 463                                     int cmd)
 464{
 465        struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
 466
 467        switch (cmd) {
 468        case SNDRV_PCM_TRIGGER_START:
 469                if (cobsc->alsa_pb_channel)
 470                        return -EBUSY;
 471                cobsc->alsa_pb_channel = true;
 472                return 0;
 473        case SNDRV_PCM_TRIGGER_STOP:
 474                cobsc->alsa_pb_channel = false;
 475                return 0;
 476        default:
 477                return -EINVAL;
 478        }
 479}
 480
 481static
 482snd_pcm_uframes_t snd_cobalt_pcm_pb_pointer(struct snd_pcm_substream *substream)
 483{
 484        struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream);
 485        size_t ptr;
 486
 487        ptr = cobsc->pb_pos;
 488
 489        return bytes_to_frames(substream->runtime, ptr) %
 490               substream->runtime->buffer_size;
 491}
 492
 493static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs,
 494                                             unsigned long offset)
 495{
 496        void *pageptr = subs->runtime->dma_area + offset;
 497
 498        return vmalloc_to_page(pageptr);
 499}
 500
 501static const struct snd_pcm_ops snd_cobalt_pcm_capture_ops = {
 502        .open           = snd_cobalt_pcm_capture_open,
 503        .close          = snd_cobalt_pcm_capture_close,
 504        .ioctl          = snd_cobalt_pcm_ioctl,
 505        .hw_params      = snd_cobalt_pcm_hw_params,
 506        .hw_free        = snd_cobalt_pcm_hw_free,
 507        .prepare        = snd_cobalt_pcm_prepare,
 508        .trigger        = snd_cobalt_pcm_trigger,
 509        .pointer        = snd_cobalt_pcm_pointer,
 510        .page           = snd_pcm_get_vmalloc_page,
 511};
 512
 513static const struct snd_pcm_ops snd_cobalt_pcm_playback_ops = {
 514        .open           = snd_cobalt_pcm_playback_open,
 515        .close          = snd_cobalt_pcm_playback_close,
 516        .ioctl          = snd_cobalt_pcm_ioctl,
 517        .hw_params      = snd_cobalt_pcm_hw_params,
 518        .hw_free        = snd_cobalt_pcm_hw_free,
 519        .prepare        = snd_cobalt_pcm_pb_prepare,
 520        .trigger        = snd_cobalt_pcm_pb_trigger,
 521        .pointer        = snd_cobalt_pcm_pb_pointer,
 522        .page           = snd_pcm_get_vmalloc_page,
 523};
 524
 525int snd_cobalt_pcm_create(struct snd_cobalt_card *cobsc)
 526{
 527        struct snd_pcm *sp;
 528        struct snd_card *sc = cobsc->sc;
 529        struct cobalt_stream *s = cobsc->s;
 530        struct cobalt *cobalt = s->cobalt;
 531        int ret;
 532
 533        s->q.gfp_flags |= __GFP_ZERO;
 534
 535        if (!s->is_output) {
 536                cobalt_s_bit_sysctrl(cobalt,
 537                        COBALT_SYS_CTRL_AUDIO_IPP_RESETN_BIT(s->video_channel),
 538                        0);
 539                mdelay(2);
 540                cobalt_s_bit_sysctrl(cobalt,
 541                        COBALT_SYS_CTRL_AUDIO_IPP_RESETN_BIT(s->video_channel),
 542                        1);
 543                mdelay(1);
 544
 545                ret = snd_pcm_new(sc, "Cobalt PCM-In HDMI",
 546                        0, /* PCM device 0, the only one for this card */
 547                        0, /* 0 playback substreams */
 548                        1, /* 1 capture substream */
 549                        &sp);
 550                if (ret) {
 551                        cobalt_err("snd_cobalt_pcm_create() failed for input with err %d\n",
 552                                   ret);
 553                        goto err_exit;
 554                }
 555
 556                snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE,
 557                                &snd_cobalt_pcm_capture_ops);
 558                sp->info_flags = 0;
 559                sp->private_data = cobsc;
 560                strscpy(sp->name, "cobalt", sizeof(sp->name));
 561        } else {
 562                cobalt_s_bit_sysctrl(cobalt,
 563                        COBALT_SYS_CTRL_AUDIO_OPP_RESETN_BIT, 0);
 564                mdelay(2);
 565                cobalt_s_bit_sysctrl(cobalt,
 566                        COBALT_SYS_CTRL_AUDIO_OPP_RESETN_BIT, 1);
 567                mdelay(1);
 568
 569                ret = snd_pcm_new(sc, "Cobalt PCM-Out HDMI",
 570                        0, /* PCM device 0, the only one for this card */
 571                        1, /* 0 playback substreams */
 572                        0, /* 1 capture substream */
 573                        &sp);
 574                if (ret) {
 575                        cobalt_err("snd_cobalt_pcm_create() failed for output with err %d\n",
 576                                   ret);
 577                        goto err_exit;
 578                }
 579
 580                snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_PLAYBACK,
 581                                &snd_cobalt_pcm_playback_ops);
 582                sp->info_flags = 0;
 583                sp->private_data = cobsc;
 584                strscpy(sp->name, "cobalt", sizeof(sp->name));
 585        }
 586
 587        return 0;
 588
 589err_exit:
 590        return ret;
 591}
 592