linux/sound/soc/sof/compress.c
<<
>>
Prefs
   1// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
   2//
   3// Copyright 2021 NXP
   4//
   5// Author: Daniel Baluta <daniel.baluta@nxp.com>
   6
   7#include <sound/soc.h>
   8#include <sound/sof.h>
   9#include <sound/compress_driver.h>
  10#include "sof-audio.h"
  11#include "sof-priv.h"
  12#include "sof-utils.h"
  13
  14static void sof_set_transferred_bytes(struct snd_compr_tstamp *tstamp,
  15                                      u64 host_pos, u64 buffer_size)
  16{
  17        u64 prev_pos;
  18        unsigned int copied;
  19
  20        div64_u64_rem(tstamp->copied_total, buffer_size, &prev_pos);
  21
  22        if (host_pos < prev_pos)
  23                copied = (buffer_size - prev_pos) + host_pos;
  24        else
  25                copied = host_pos - prev_pos;
  26
  27        tstamp->copied_total += copied;
  28}
  29
  30static void snd_sof_compr_fragment_elapsed_work(struct work_struct *work)
  31{
  32        struct snd_sof_pcm_stream *sps =
  33                container_of(work, struct snd_sof_pcm_stream,
  34                             period_elapsed_work);
  35
  36        snd_compr_fragment_elapsed(sps->cstream);
  37}
  38
  39void snd_sof_compr_init_elapsed_work(struct work_struct *work)
  40{
  41        INIT_WORK(work, snd_sof_compr_fragment_elapsed_work);
  42}
  43
  44/*
  45 * sof compr fragment elapse, this could be called in irq thread context
  46 */
  47void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream)
  48{
  49        struct snd_soc_pcm_runtime *rtd;
  50        struct snd_compr_runtime *crtd;
  51        struct snd_soc_component *component;
  52        struct snd_compr_tstamp *tstamp;
  53        struct snd_sof_pcm *spcm;
  54
  55        if (!cstream)
  56                return;
  57
  58        rtd = cstream->private_data;
  59        crtd = cstream->runtime;
  60        tstamp = crtd->private_data;
  61        component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
  62
  63        spcm = snd_sof_find_spcm_dai(component, rtd);
  64        if (!spcm) {
  65                dev_err(component->dev,
  66                        "fragment elapsed called for unknown stream!\n");
  67                return;
  68        }
  69
  70        sof_set_transferred_bytes(tstamp, spcm->stream[cstream->direction].posn.host_posn,
  71                                  crtd->buffer_size);
  72
  73        /* use the same workqueue-based solution as for PCM, cf. snd_sof_pcm_elapsed */
  74        schedule_work(&spcm->stream[cstream->direction].period_elapsed_work);
  75}
  76
  77static int create_page_table(struct snd_soc_component *component,
  78                             struct snd_compr_stream *cstream,
  79                             unsigned char *dma_area, size_t size)
  80{
  81        struct snd_dma_buffer *dmab = cstream->runtime->dma_buffer_p;
  82        struct snd_soc_pcm_runtime *rtd = cstream->private_data;
  83        int dir = cstream->direction;
  84        struct snd_sof_pcm *spcm;
  85
  86        spcm = snd_sof_find_spcm_dai(component, rtd);
  87        if (!spcm)
  88                return -EINVAL;
  89
  90        return snd_sof_create_page_table(component->dev, dmab,
  91                                         spcm->stream[dir].page_table.area, size);
  92}
  93
  94static int sof_compr_open(struct snd_soc_component *component,
  95                          struct snd_compr_stream *cstream)
  96{
  97        struct snd_soc_pcm_runtime *rtd = cstream->private_data;
  98        struct snd_compr_runtime *crtd = cstream->runtime;
  99        struct snd_compr_tstamp *tstamp;
 100        struct snd_sof_pcm *spcm;
 101        int dir;
 102
 103        tstamp = kzalloc(sizeof(*tstamp), GFP_KERNEL);
 104        if (!tstamp)
 105                return -ENOMEM;
 106
 107        spcm = snd_sof_find_spcm_dai(component, rtd);
 108        if (!spcm) {
 109                kfree(tstamp);
 110                return -EINVAL;
 111        }
 112
 113        dir = cstream->direction;
 114
 115        if (spcm->stream[dir].cstream) {
 116                kfree(tstamp);
 117                return -EBUSY;
 118        }
 119
 120        spcm->stream[dir].cstream = cstream;
 121        spcm->stream[dir].posn.host_posn = 0;
 122        spcm->stream[dir].posn.dai_posn = 0;
 123        spcm->prepared[dir] = false;
 124
 125        crtd->private_data = tstamp;
 126
 127        return 0;
 128}
 129
 130static int sof_compr_free(struct snd_soc_component *component,
 131                          struct snd_compr_stream *cstream)
 132{
 133        struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
 134        struct snd_compr_tstamp *tstamp = cstream->runtime->private_data;
 135        struct snd_soc_pcm_runtime *rtd = cstream->private_data;
 136        struct sof_ipc_stream stream;
 137        struct sof_ipc_reply reply;
 138        struct snd_sof_pcm *spcm;
 139        int ret = 0;
 140
 141        spcm = snd_sof_find_spcm_dai(component, rtd);
 142        if (!spcm)
 143                return -EINVAL;
 144
 145        stream.hdr.size = sizeof(stream);
 146        stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE;
 147        stream.comp_id = spcm->stream[cstream->direction].comp_id;
 148
 149        if (spcm->prepared[cstream->direction]) {
 150                ret = sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream),
 151                                         &reply, sizeof(reply));
 152                if (!ret)
 153                        spcm->prepared[cstream->direction] = false;
 154        }
 155
 156        cancel_work_sync(&spcm->stream[cstream->direction].period_elapsed_work);
 157        spcm->stream[cstream->direction].cstream = NULL;
 158        kfree(tstamp);
 159
 160        return ret;
 161}
 162
 163static int sof_compr_set_params(struct snd_soc_component *component,
 164                                struct snd_compr_stream *cstream, struct snd_compr_params *params)
 165{
 166        struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
 167        struct snd_soc_pcm_runtime *rtd = cstream->private_data;
 168        struct snd_compr_runtime *crtd = cstream->runtime;
 169        struct sof_ipc_pcm_params_reply ipc_params_reply;
 170        struct snd_compr_tstamp *tstamp;
 171        struct sof_ipc_pcm_params pcm;
 172        struct snd_sof_pcm *spcm;
 173        int ret;
 174
 175        tstamp = crtd->private_data;
 176
 177        spcm = snd_sof_find_spcm_dai(component, rtd);
 178
 179        if (!spcm)
 180                return -EINVAL;
 181
 182        cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG;
 183        cstream->dma_buffer.dev.dev = sdev->dev;
 184        ret = snd_compr_malloc_pages(cstream, crtd->buffer_size);
 185        if (ret < 0)
 186                return ret;
 187
 188        ret = create_page_table(component, cstream, crtd->dma_area, crtd->dma_bytes);
 189        if (ret < 0)
 190                return ret;
 191
 192        memset(&pcm, 0, sizeof(pcm));
 193
 194        pcm.params.buffer.pages = PFN_UP(crtd->dma_bytes);
 195        pcm.hdr.size = sizeof(pcm);
 196        pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS;
 197
 198        pcm.comp_id = spcm->stream[cstream->direction].comp_id;
 199        pcm.params.hdr.size = sizeof(pcm.params);
 200        pcm.params.buffer.phy_addr = spcm->stream[cstream->direction].page_table.addr;
 201        pcm.params.buffer.size = crtd->dma_bytes;
 202        pcm.params.direction = cstream->direction;
 203        pcm.params.channels = params->codec.ch_out;
 204        pcm.params.rate = params->codec.sample_rate;
 205        pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED;
 206        pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE;
 207        pcm.params.sample_container_bytes =
 208                snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S32) >> 3;
 209        pcm.params.host_period_bytes = params->buffer.fragment_size;
 210
 211        ret = sof_ipc_tx_message(sdev->ipc, &pcm, sizeof(pcm),
 212                                 &ipc_params_reply, sizeof(ipc_params_reply));
 213        if (ret < 0) {
 214                dev_err(component->dev, "error ipc failed\n");
 215                return ret;
 216        }
 217
 218        tstamp->byte_offset = sdev->stream_box.offset + ipc_params_reply.posn_offset;
 219        tstamp->sampling_rate = params->codec.sample_rate;
 220
 221        spcm->prepared[cstream->direction] = true;
 222
 223        return 0;
 224}
 225
 226static int sof_compr_get_params(struct snd_soc_component *component,
 227                                struct snd_compr_stream *cstream, struct snd_codec *params)
 228{
 229        /* TODO: we don't query the supported codecs for now, if the
 230         * application asks for an unsupported codec the set_params() will fail.
 231         */
 232        return 0;
 233}
 234
 235static int sof_compr_trigger(struct snd_soc_component *component,
 236                             struct snd_compr_stream *cstream, int cmd)
 237{
 238        struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
 239        struct snd_soc_pcm_runtime *rtd = cstream->private_data;
 240        struct sof_ipc_stream stream;
 241        struct sof_ipc_reply reply;
 242        struct snd_sof_pcm *spcm;
 243
 244        spcm = snd_sof_find_spcm_dai(component, rtd);
 245        if (!spcm)
 246                return -EINVAL;
 247
 248        stream.hdr.size = sizeof(stream);
 249        stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG;
 250        stream.comp_id = spcm->stream[cstream->direction].comp_id;
 251
 252        switch (cmd) {
 253        case SNDRV_PCM_TRIGGER_START:
 254                stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START;
 255                break;
 256        case SNDRV_PCM_TRIGGER_STOP:
 257                stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP;
 258                break;
 259        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 260                stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE;
 261                break;
 262        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 263                stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE;
 264                break;
 265        default:
 266                dev_err(component->dev, "error: unhandled trigger cmd %d\n", cmd);
 267                break;
 268        }
 269
 270        return sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream),
 271                                  &reply, sizeof(reply));
 272}
 273
 274static int sof_compr_copy(struct snd_soc_component *component,
 275                          struct snd_compr_stream *cstream,
 276                          char __user *buf, size_t count)
 277{
 278        struct snd_compr_runtime *rtd = cstream->runtime;
 279        unsigned int offset, n;
 280        void *ptr;
 281        int ret;
 282
 283        if (count > rtd->buffer_size)
 284                count = rtd->buffer_size;
 285
 286        div_u64_rem(rtd->total_bytes_available, rtd->buffer_size, &offset);
 287        ptr = rtd->dma_area + offset;
 288        n = rtd->buffer_size - offset;
 289
 290        if (count < n) {
 291                ret = copy_from_user(ptr, buf, count);
 292        } else {
 293                ret = copy_from_user(ptr, buf, n);
 294                ret += copy_from_user(rtd->dma_area, buf + n, count - n);
 295        }
 296
 297        return count - ret;
 298}
 299
 300static int sof_compr_pointer(struct snd_soc_component *component,
 301                             struct snd_compr_stream *cstream,
 302                             struct snd_compr_tstamp *tstamp)
 303{
 304        struct snd_compr_tstamp *pstamp = cstream->runtime->private_data;
 305
 306        tstamp->sampling_rate = pstamp->sampling_rate;
 307        tstamp->copied_total = pstamp->copied_total;
 308
 309        return 0;
 310}
 311
 312struct snd_compress_ops sof_compressed_ops = {
 313        .open           = sof_compr_open,
 314        .free           = sof_compr_free,
 315        .set_params     = sof_compr_set_params,
 316        .get_params     = sof_compr_get_params,
 317        .trigger        = sof_compr_trigger,
 318        .pointer        = sof_compr_pointer,
 319        .copy           = sof_compr_copy,
 320};
 321EXPORT_SYMBOL(sof_compressed_ops);
 322