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