// SPDX-License-Identifier: GPL-2.0-only 
 | 
/* 
 | 
 *  ALSA PCM device for the 
 | 
 *  ALSA interface to cobalt PCM capture streams 
 | 
 * 
 | 
 *  Copyright 2014-2015 Cisco Systems, Inc. and/or its affiliates. 
 | 
 *  All rights reserved. 
 | 
 */ 
 | 
  
 | 
#include <linux/init.h> 
 | 
#include <linux/kernel.h> 
 | 
#include <linux/delay.h> 
 | 
  
 | 
#include <media/v4l2-device.h> 
 | 
  
 | 
#include <sound/core.h> 
 | 
#include <sound/pcm.h> 
 | 
  
 | 
#include "cobalt-driver.h" 
 | 
#include "cobalt-alsa.h" 
 | 
#include "cobalt-alsa-pcm.h" 
 | 
  
 | 
static unsigned int pcm_debug; 
 | 
module_param(pcm_debug, int, 0644); 
 | 
MODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm"); 
 | 
  
 | 
#define dprintk(fmt, arg...) \ 
 | 
    do { \ 
 | 
        if (pcm_debug) \ 
 | 
            pr_info("cobalt-alsa-pcm %s: " fmt, __func__, ##arg); \ 
 | 
    } while (0) 
 | 
  
 | 
static const struct snd_pcm_hardware snd_cobalt_hdmi_capture = { 
 | 
    .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | 
 | 
        SNDRV_PCM_INFO_MMAP           | 
 | 
        SNDRV_PCM_INFO_INTERLEAVED    | 
 | 
        SNDRV_PCM_INFO_MMAP_VALID, 
 | 
  
 | 
    .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, 
 | 
  
 | 
    .rates = SNDRV_PCM_RATE_48000, 
 | 
  
 | 
    .rate_min = 48000, 
 | 
    .rate_max = 48000, 
 | 
    .channels_min = 1, 
 | 
    .channels_max = 8, 
 | 
    .buffer_bytes_max = 4 * 240 * 8 * 4,    /* 5 ms of data */ 
 | 
    .period_bytes_min = 1920,        /* 1 sample = 8 * 4 bytes */ 
 | 
    .period_bytes_max = 240 * 8 * 4,    /* 5 ms of 8 channel data */ 
 | 
    .periods_min = 1, 
 | 
    .periods_max = 4, 
 | 
}; 
 | 
  
 | 
static const struct snd_pcm_hardware snd_cobalt_playback = { 
 | 
    .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | 
 | 
        SNDRV_PCM_INFO_MMAP           | 
 | 
        SNDRV_PCM_INFO_INTERLEAVED    | 
 | 
        SNDRV_PCM_INFO_MMAP_VALID, 
 | 
  
 | 
    .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, 
 | 
  
 | 
    .rates = SNDRV_PCM_RATE_48000, 
 | 
  
 | 
    .rate_min = 48000, 
 | 
    .rate_max = 48000, 
 | 
    .channels_min = 1, 
 | 
    .channels_max = 8, 
 | 
    .buffer_bytes_max = 4 * 240 * 8 * 4,    /* 5 ms of data */ 
 | 
    .period_bytes_min = 1920,        /* 1 sample = 8 * 4 bytes */ 
 | 
    .period_bytes_max = 240 * 8 * 4,    /* 5 ms of 8 channel data */ 
 | 
    .periods_min = 1, 
 | 
    .periods_max = 4, 
 | 
}; 
 | 
  
 | 
static void sample_cpy(u8 *dst, const u8 *src, u32 len, bool is_s32) 
 | 
{ 
 | 
    static const unsigned map[8] = { 0, 1, 5, 4, 2, 3, 6, 7 }; 
 | 
    unsigned idx = 0; 
 | 
  
 | 
    while (len >= (is_s32 ? 4 : 2)) { 
 | 
        unsigned offset = map[idx] * 4; 
 | 
        u32 val = src[offset + 1] + (src[offset + 2] << 8) + 
 | 
             (src[offset + 3] << 16); 
 | 
  
 | 
        if (is_s32) { 
 | 
            *dst++ = 0; 
 | 
            *dst++ = val & 0xff; 
 | 
        } 
 | 
        *dst++ = (val >> 8) & 0xff; 
 | 
        *dst++ = (val >> 16) & 0xff; 
 | 
        len -= is_s32 ? 4 : 2; 
 | 
        idx++; 
 | 
    } 
 | 
} 
 | 
  
 | 
static void cobalt_alsa_announce_pcm_data(struct snd_cobalt_card *cobsc, 
 | 
                    u8 *pcm_data, 
 | 
                    size_t skip, 
 | 
                    size_t samples) 
 | 
{ 
 | 
    struct snd_pcm_substream *substream; 
 | 
    struct snd_pcm_runtime *runtime; 
 | 
    unsigned long flags; 
 | 
    unsigned int oldptr; 
 | 
    unsigned int stride; 
 | 
    int length = samples; 
 | 
    int period_elapsed = 0; 
 | 
    bool is_s32; 
 | 
  
 | 
    dprintk("cobalt alsa announce ptr=%p data=%p num_bytes=%zd\n", cobsc, 
 | 
        pcm_data, samples); 
 | 
  
 | 
    substream = cobsc->capture_pcm_substream; 
 | 
    if (substream == NULL) { 
 | 
        dprintk("substream was NULL\n"); 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    runtime = substream->runtime; 
 | 
    if (runtime == NULL) { 
 | 
        dprintk("runtime was NULL\n"); 
 | 
        return; 
 | 
    } 
 | 
    is_s32 = runtime->format == SNDRV_PCM_FORMAT_S32_LE; 
 | 
  
 | 
    stride = runtime->frame_bits >> 3; 
 | 
    if (stride == 0) { 
 | 
        dprintk("stride is zero\n"); 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    if (length == 0) { 
 | 
        dprintk("%s: length was zero\n", __func__); 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    if (runtime->dma_area == NULL) { 
 | 
        dprintk("dma area was NULL - ignoring\n"); 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    oldptr = cobsc->hwptr_done_capture; 
 | 
    if (oldptr + length >= runtime->buffer_size) { 
 | 
        unsigned int cnt = runtime->buffer_size - oldptr; 
 | 
        unsigned i; 
 | 
  
 | 
        for (i = 0; i < cnt; i++) 
 | 
            sample_cpy(runtime->dma_area + (oldptr + i) * stride, 
 | 
                    pcm_data + i * skip, 
 | 
                    stride, is_s32); 
 | 
        for (i = cnt; i < length; i++) 
 | 
            sample_cpy(runtime->dma_area + (i - cnt) * stride, 
 | 
                    pcm_data + i * skip, stride, is_s32); 
 | 
    } else { 
 | 
        unsigned i; 
 | 
  
 | 
        for (i = 0; i < length; i++) 
 | 
            sample_cpy(runtime->dma_area + (oldptr + i) * stride, 
 | 
                    pcm_data + i * skip, 
 | 
                    stride, is_s32); 
 | 
    } 
 | 
    snd_pcm_stream_lock_irqsave(substream, flags); 
 | 
  
 | 
    cobsc->hwptr_done_capture += length; 
 | 
    if (cobsc->hwptr_done_capture >= 
 | 
        runtime->buffer_size) 
 | 
        cobsc->hwptr_done_capture -= 
 | 
            runtime->buffer_size; 
 | 
  
 | 
    cobsc->capture_transfer_done += length; 
 | 
    if (cobsc->capture_transfer_done >= 
 | 
        runtime->period_size) { 
 | 
        cobsc->capture_transfer_done -= 
 | 
            runtime->period_size; 
 | 
        period_elapsed = 1; 
 | 
    } 
 | 
  
 | 
    snd_pcm_stream_unlock_irqrestore(substream, flags); 
 | 
  
 | 
    if (period_elapsed) 
 | 
        snd_pcm_period_elapsed(substream); 
 | 
} 
 | 
  
 | 
static int alsa_fnc(struct vb2_buffer *vb, void *priv) 
 | 
{ 
 | 
    struct cobalt_stream *s = priv; 
 | 
    unsigned char *p = vb2_plane_vaddr(vb, 0); 
 | 
    int i; 
 | 
  
 | 
    if (pcm_debug) { 
 | 
        pr_info("alsa: "); 
 | 
        for (i = 0; i < 8 * 4; i++) { 
 | 
            if (!(i & 3)) 
 | 
                pr_cont(" "); 
 | 
            pr_cont("%02x", p[i]); 
 | 
        } 
 | 
        pr_cont("\n"); 
 | 
    } 
 | 
    cobalt_alsa_announce_pcm_data(s->alsa, 
 | 
            vb2_plane_vaddr(vb, 0), 
 | 
            8 * 4, 
 | 
            vb2_get_plane_payload(vb, 0) / (8 * 4)); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int snd_cobalt_pcm_capture_open(struct snd_pcm_substream *substream) 
 | 
{ 
 | 
    struct snd_pcm_runtime *runtime = substream->runtime; 
 | 
    struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); 
 | 
    struct cobalt_stream *s = cobsc->s; 
 | 
  
 | 
    runtime->hw = snd_cobalt_hdmi_capture; 
 | 
    snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); 
 | 
    cobsc->capture_pcm_substream = substream; 
 | 
    runtime->private_data = s; 
 | 
    cobsc->alsa_record_cnt++; 
 | 
    if (cobsc->alsa_record_cnt == 1) { 
 | 
        int rc; 
 | 
  
 | 
        rc = vb2_thread_start(&s->q, alsa_fnc, s, s->vdev.name); 
 | 
        if (rc) { 
 | 
            cobsc->alsa_record_cnt--; 
 | 
            return rc; 
 | 
        } 
 | 
    } 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int snd_cobalt_pcm_capture_close(struct snd_pcm_substream *substream) 
 | 
{ 
 | 
    struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); 
 | 
    struct cobalt_stream *s = cobsc->s; 
 | 
  
 | 
    cobsc->alsa_record_cnt--; 
 | 
    if (cobsc->alsa_record_cnt == 0) 
 | 
        vb2_thread_stop(&s->q); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int snd_cobalt_pcm_prepare(struct snd_pcm_substream *substream) 
 | 
{ 
 | 
    struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); 
 | 
  
 | 
    cobsc->hwptr_done_capture = 0; 
 | 
    cobsc->capture_transfer_done = 0; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int snd_cobalt_pcm_trigger(struct snd_pcm_substream *substream, int cmd) 
 | 
{ 
 | 
    switch (cmd) { 
 | 
    case SNDRV_PCM_TRIGGER_START: 
 | 
    case SNDRV_PCM_TRIGGER_STOP: 
 | 
        return 0; 
 | 
    default: 
 | 
        return -EINVAL; 
 | 
    } 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static 
 | 
snd_pcm_uframes_t snd_cobalt_pcm_pointer(struct snd_pcm_substream *substream) 
 | 
{ 
 | 
    snd_pcm_uframes_t hwptr_done; 
 | 
    struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); 
 | 
  
 | 
    hwptr_done = cobsc->hwptr_done_capture; 
 | 
  
 | 
    return hwptr_done; 
 | 
} 
 | 
  
 | 
static void pb_sample_cpy(u8 *dst, const u8 *src, u32 len, bool is_s32) 
 | 
{ 
 | 
    static const unsigned map[8] = { 0, 1, 5, 4, 2, 3, 6, 7 }; 
 | 
    unsigned idx = 0; 
 | 
  
 | 
    while (len >= (is_s32 ? 4 : 2)) { 
 | 
        unsigned offset = map[idx] * 4; 
 | 
        u8 *out = dst + offset; 
 | 
  
 | 
        *out++ = 0; 
 | 
        if (is_s32) { 
 | 
            src++; 
 | 
            *out++ = *src++; 
 | 
        } else { 
 | 
            *out++ = 0; 
 | 
        } 
 | 
        *out++ = *src++; 
 | 
        *out = *src++; 
 | 
        len -= is_s32 ? 4 : 2; 
 | 
        idx++; 
 | 
    } 
 | 
} 
 | 
  
 | 
static void cobalt_alsa_pb_pcm_data(struct snd_cobalt_card *cobsc, 
 | 
                    u8 *pcm_data, 
 | 
                    size_t skip, 
 | 
                    size_t samples) 
 | 
{ 
 | 
    struct snd_pcm_substream *substream; 
 | 
    struct snd_pcm_runtime *runtime; 
 | 
    unsigned long flags; 
 | 
    unsigned int pos; 
 | 
    unsigned int stride; 
 | 
    bool is_s32; 
 | 
    unsigned i; 
 | 
  
 | 
    dprintk("cobalt alsa pb ptr=%p data=%p samples=%zd\n", cobsc, 
 | 
        pcm_data, samples); 
 | 
  
 | 
    substream = cobsc->playback_pcm_substream; 
 | 
    if (substream == NULL) { 
 | 
        dprintk("substream was NULL\n"); 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    runtime = substream->runtime; 
 | 
    if (runtime == NULL) { 
 | 
        dprintk("runtime was NULL\n"); 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    is_s32 = runtime->format == SNDRV_PCM_FORMAT_S32_LE; 
 | 
    stride = runtime->frame_bits >> 3; 
 | 
    if (stride == 0) { 
 | 
        dprintk("stride is zero\n"); 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    if (samples == 0) { 
 | 
        dprintk("%s: samples was zero\n", __func__); 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    if (runtime->dma_area == NULL) { 
 | 
        dprintk("dma area was NULL - ignoring\n"); 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    pos = cobsc->pb_pos % cobsc->pb_size; 
 | 
    for (i = 0; i < cobsc->pb_count / (8 * 4); i++) 
 | 
        pb_sample_cpy(pcm_data + i * skip, 
 | 
                runtime->dma_area + pos + i * stride, 
 | 
                stride, is_s32); 
 | 
    snd_pcm_stream_lock_irqsave(substream, flags); 
 | 
  
 | 
    cobsc->pb_pos += i * stride; 
 | 
  
 | 
    snd_pcm_stream_unlock_irqrestore(substream, flags); 
 | 
    if (cobsc->pb_pos % cobsc->pb_count == 0) 
 | 
        snd_pcm_period_elapsed(substream); 
 | 
} 
 | 
  
 | 
static int alsa_pb_fnc(struct vb2_buffer *vb, void *priv) 
 | 
{ 
 | 
    struct cobalt_stream *s = priv; 
 | 
  
 | 
    if (s->alsa->alsa_pb_channel) 
 | 
        cobalt_alsa_pb_pcm_data(s->alsa, 
 | 
                vb2_plane_vaddr(vb, 0), 
 | 
                8 * 4, 
 | 
                vb2_get_plane_payload(vb, 0) / (8 * 4)); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int snd_cobalt_pcm_playback_open(struct snd_pcm_substream *substream) 
 | 
{ 
 | 
    struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); 
 | 
    struct snd_pcm_runtime *runtime = substream->runtime; 
 | 
    struct cobalt_stream *s = cobsc->s; 
 | 
  
 | 
    runtime->hw = snd_cobalt_playback; 
 | 
    snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); 
 | 
    cobsc->playback_pcm_substream = substream; 
 | 
    runtime->private_data = s; 
 | 
    cobsc->alsa_playback_cnt++; 
 | 
    if (cobsc->alsa_playback_cnt == 1) { 
 | 
        int rc; 
 | 
  
 | 
        rc = vb2_thread_start(&s->q, alsa_pb_fnc, s, s->vdev.name); 
 | 
        if (rc) { 
 | 
            cobsc->alsa_playback_cnt--; 
 | 
            return rc; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int snd_cobalt_pcm_playback_close(struct snd_pcm_substream *substream) 
 | 
{ 
 | 
    struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); 
 | 
    struct cobalt_stream *s = cobsc->s; 
 | 
  
 | 
    cobsc->alsa_playback_cnt--; 
 | 
    if (cobsc->alsa_playback_cnt == 0) 
 | 
        vb2_thread_stop(&s->q); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int snd_cobalt_pcm_pb_prepare(struct snd_pcm_substream *substream) 
 | 
{ 
 | 
    struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); 
 | 
  
 | 
    cobsc->pb_size = snd_pcm_lib_buffer_bytes(substream); 
 | 
    cobsc->pb_count = snd_pcm_lib_period_bytes(substream); 
 | 
    cobsc->pb_pos = 0; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int snd_cobalt_pcm_pb_trigger(struct snd_pcm_substream *substream, 
 | 
                     int cmd) 
 | 
{ 
 | 
    struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); 
 | 
  
 | 
    switch (cmd) { 
 | 
    case SNDRV_PCM_TRIGGER_START: 
 | 
        if (cobsc->alsa_pb_channel) 
 | 
            return -EBUSY; 
 | 
        cobsc->alsa_pb_channel = true; 
 | 
        return 0; 
 | 
    case SNDRV_PCM_TRIGGER_STOP: 
 | 
        cobsc->alsa_pb_channel = false; 
 | 
        return 0; 
 | 
    default: 
 | 
        return -EINVAL; 
 | 
    } 
 | 
} 
 | 
  
 | 
static 
 | 
snd_pcm_uframes_t snd_cobalt_pcm_pb_pointer(struct snd_pcm_substream *substream) 
 | 
{ 
 | 
    struct snd_cobalt_card *cobsc = snd_pcm_substream_chip(substream); 
 | 
    size_t ptr; 
 | 
  
 | 
    ptr = cobsc->pb_pos; 
 | 
  
 | 
    return bytes_to_frames(substream->runtime, ptr) % 
 | 
           substream->runtime->buffer_size; 
 | 
} 
 | 
  
 | 
static const struct snd_pcm_ops snd_cobalt_pcm_capture_ops = { 
 | 
    .open        = snd_cobalt_pcm_capture_open, 
 | 
    .close        = snd_cobalt_pcm_capture_close, 
 | 
    .prepare    = snd_cobalt_pcm_prepare, 
 | 
    .trigger    = snd_cobalt_pcm_trigger, 
 | 
    .pointer    = snd_cobalt_pcm_pointer, 
 | 
}; 
 | 
  
 | 
static const struct snd_pcm_ops snd_cobalt_pcm_playback_ops = { 
 | 
    .open        = snd_cobalt_pcm_playback_open, 
 | 
    .close        = snd_cobalt_pcm_playback_close, 
 | 
    .prepare    = snd_cobalt_pcm_pb_prepare, 
 | 
    .trigger    = snd_cobalt_pcm_pb_trigger, 
 | 
    .pointer    = snd_cobalt_pcm_pb_pointer, 
 | 
}; 
 | 
  
 | 
int snd_cobalt_pcm_create(struct snd_cobalt_card *cobsc) 
 | 
{ 
 | 
    struct snd_pcm *sp; 
 | 
    struct snd_card *sc = cobsc->sc; 
 | 
    struct cobalt_stream *s = cobsc->s; 
 | 
    struct cobalt *cobalt = s->cobalt; 
 | 
    int ret; 
 | 
  
 | 
    s->q.gfp_flags |= __GFP_ZERO; 
 | 
  
 | 
    if (!s->is_output) { 
 | 
        cobalt_s_bit_sysctrl(cobalt, 
 | 
            COBALT_SYS_CTRL_AUDIO_IPP_RESETN_BIT(s->video_channel), 
 | 
            0); 
 | 
        mdelay(2); 
 | 
        cobalt_s_bit_sysctrl(cobalt, 
 | 
            COBALT_SYS_CTRL_AUDIO_IPP_RESETN_BIT(s->video_channel), 
 | 
            1); 
 | 
        mdelay(1); 
 | 
  
 | 
        ret = snd_pcm_new(sc, "Cobalt PCM-In HDMI", 
 | 
            0, /* PCM device 0, the only one for this card */ 
 | 
            0, /* 0 playback substreams */ 
 | 
            1, /* 1 capture substream */ 
 | 
            &sp); 
 | 
        if (ret) { 
 | 
            cobalt_err("snd_cobalt_pcm_create() failed for input with err %d\n", 
 | 
                   ret); 
 | 
            goto err_exit; 
 | 
        } 
 | 
  
 | 
        snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_CAPTURE, 
 | 
                &snd_cobalt_pcm_capture_ops); 
 | 
        snd_pcm_set_managed_buffer_all(sp, SNDRV_DMA_TYPE_VMALLOC, 
 | 
                           NULL, 0, 0); 
 | 
        sp->info_flags = 0; 
 | 
        sp->private_data = cobsc; 
 | 
        strscpy(sp->name, "cobalt", sizeof(sp->name)); 
 | 
    } else { 
 | 
        cobalt_s_bit_sysctrl(cobalt, 
 | 
            COBALT_SYS_CTRL_AUDIO_OPP_RESETN_BIT, 0); 
 | 
        mdelay(2); 
 | 
        cobalt_s_bit_sysctrl(cobalt, 
 | 
            COBALT_SYS_CTRL_AUDIO_OPP_RESETN_BIT, 1); 
 | 
        mdelay(1); 
 | 
  
 | 
        ret = snd_pcm_new(sc, "Cobalt PCM-Out HDMI", 
 | 
            0, /* PCM device 0, the only one for this card */ 
 | 
            1, /* 0 playback substreams */ 
 | 
            0, /* 1 capture substream */ 
 | 
            &sp); 
 | 
        if (ret) { 
 | 
            cobalt_err("snd_cobalt_pcm_create() failed for output with err %d\n", 
 | 
                   ret); 
 | 
            goto err_exit; 
 | 
        } 
 | 
  
 | 
        snd_pcm_set_ops(sp, SNDRV_PCM_STREAM_PLAYBACK, 
 | 
                &snd_cobalt_pcm_playback_ops); 
 | 
        snd_pcm_set_managed_buffer_all(sp, SNDRV_DMA_TYPE_VMALLOC, 
 | 
                           NULL, 0, 0); 
 | 
        sp->info_flags = 0; 
 | 
        sp->private_data = cobsc; 
 | 
        strscpy(sp->name, "cobalt", sizeof(sp->name)); 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
  
 | 
err_exit: 
 | 
    return ret; 
 | 
} 
 |