linux/sound/soc/sof/intel/hda-pcm.c
<<
>>
Prefs
   1// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
   2//
   3// This file is provided under a dual BSD/GPLv2 license.  When using or
   4// redistributing this file, you may do so under either license.
   5//
   6// Copyright(c) 2018 Intel Corporation. All rights reserved.
   7//
   8// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com>
   9//          Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
  10//          Rander Wang <rander.wang@intel.com>
  11//          Keyon Jie <yang.jie@linux.intel.com>
  12//
  13
  14/*
  15 * Hardware interface for generic Intel audio DSP HDA IP
  16 */
  17
  18#include <sound/hda_register.h>
  19#include <sound/pcm_params.h>
  20#include "../ops.h"
  21#include "hda.h"
  22
  23#define SDnFMT_BASE(x)  ((x) << 14)
  24#define SDnFMT_MULT(x)  (((x) - 1) << 11)
  25#define SDnFMT_DIV(x)   (((x) - 1) << 8)
  26#define SDnFMT_BITS(x)  ((x) << 4)
  27#define SDnFMT_CHAN(x)  ((x) << 0)
  28
  29static inline u32 get_mult_div(struct snd_sof_dev *sdev, int rate)
  30{
  31        switch (rate) {
  32        case 8000:
  33                return SDnFMT_DIV(6);
  34        case 9600:
  35                return SDnFMT_DIV(5);
  36        case 11025:
  37                return SDnFMT_BASE(1) | SDnFMT_DIV(4);
  38        case 16000:
  39                return SDnFMT_DIV(3);
  40        case 22050:
  41                return SDnFMT_BASE(1) | SDnFMT_DIV(2);
  42        case 32000:
  43                return SDnFMT_DIV(3) | SDnFMT_MULT(2);
  44        case 44100:
  45                return SDnFMT_BASE(1);
  46        case 48000:
  47                return 0;
  48        case 88200:
  49                return SDnFMT_BASE(1) | SDnFMT_MULT(2);
  50        case 96000:
  51                return SDnFMT_MULT(2);
  52        case 176400:
  53                return SDnFMT_BASE(1) | SDnFMT_MULT(4);
  54        case 192000:
  55                return SDnFMT_MULT(4);
  56        default:
  57                dev_warn(sdev->dev, "can't find div rate %d using 48kHz\n",
  58                         rate);
  59                return 0; /* use 48KHz if not found */
  60        }
  61};
  62
  63static inline u32 get_bits(struct snd_sof_dev *sdev, int sample_bits)
  64{
  65        switch (sample_bits) {
  66        case 8:
  67                return SDnFMT_BITS(0);
  68        case 16:
  69                return SDnFMT_BITS(1);
  70        case 20:
  71                return SDnFMT_BITS(2);
  72        case 24:
  73                return SDnFMT_BITS(3);
  74        case 32:
  75                return SDnFMT_BITS(4);
  76        default:
  77                dev_warn(sdev->dev, "can't find %d bits using 16bit\n",
  78                         sample_bits);
  79                return SDnFMT_BITS(1); /* use 16bits format if not found */
  80        }
  81};
  82
  83int hda_dsp_pcm_hw_params(struct snd_sof_dev *sdev,
  84                          struct snd_pcm_substream *substream,
  85                          struct snd_pcm_hw_params *params,
  86                          struct sof_ipc_stream_params *ipc_params)
  87{
  88        struct hdac_stream *hstream = substream->runtime->private_data;
  89        struct hdac_ext_stream *stream = stream_to_hdac_ext_stream(hstream);
  90        struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
  91        struct snd_dma_buffer *dmab;
  92        int ret;
  93        u32 size, rate, bits;
  94
  95        size = params_buffer_bytes(params);
  96        rate = get_mult_div(sdev, params_rate(params));
  97        bits = get_bits(sdev, params_width(params));
  98
  99        hstream->substream = substream;
 100
 101        dmab = substream->runtime->dma_buffer_p;
 102
 103        hstream->format_val = rate | bits | (params_channels(params) - 1);
 104        hstream->bufsize = size;
 105        hstream->period_bytes = params_period_bytes(params);
 106        hstream->no_period_wakeup  =
 107                        (params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) &&
 108                        (params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP);
 109
 110        ret = hda_dsp_stream_hw_params(sdev, stream, dmab, params);
 111        if (ret < 0) {
 112                dev_err(sdev->dev, "error: hdac prepare failed: %x\n", ret);
 113                return ret;
 114        }
 115
 116        /* disable SPIB, to enable buffer wrap for stream */
 117        hda_dsp_stream_spib_config(sdev, stream, HDA_DSP_SPIB_DISABLE, 0);
 118
 119        /* set host_period_bytes to 0 if no IPC position */
 120        if (hda && hda->no_ipc_position)
 121                ipc_params->host_period_bytes = 0;
 122
 123        ipc_params->stream_tag = hstream->stream_tag;
 124
 125        return 0;
 126}
 127
 128int hda_dsp_pcm_trigger(struct snd_sof_dev *sdev,
 129                        struct snd_pcm_substream *substream, int cmd)
 130{
 131        struct hdac_stream *hstream = substream->runtime->private_data;
 132        struct hdac_ext_stream *stream = stream_to_hdac_ext_stream(hstream);
 133
 134        return hda_dsp_stream_trigger(sdev, stream, cmd);
 135}
 136
 137snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev,
 138                                      struct snd_pcm_substream *substream)
 139{
 140        struct snd_soc_pcm_runtime *rtd = substream->private_data;
 141        struct hdac_stream *hstream = substream->runtime->private_data;
 142        struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
 143        struct snd_sof_pcm *spcm;
 144        snd_pcm_uframes_t pos;
 145
 146        spcm = snd_sof_find_spcm_dai(sdev, rtd);
 147        if (!spcm) {
 148                dev_warn_ratelimited(sdev->dev, "warn: can't find PCM with DAI ID %d\n",
 149                                     rtd->dai_link->id);
 150                return 0;
 151        }
 152
 153        if (hda && !hda->no_ipc_position) {
 154                /* read position from IPC position */
 155                pos = spcm->stream[substream->stream].posn.host_posn;
 156                goto found;
 157        }
 158
 159        /*
 160         * DPIB/posbuf position mode:
 161         * For Playback, Use DPIB register from HDA space which
 162         * reflects the actual data transferred.
 163         * For Capture, Use the position buffer for pointer, as DPIB
 164         * is not accurate enough, its update may be completed
 165         * earlier than the data written to DDR.
 166         */
 167        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 168                pos = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
 169                                       AZX_REG_VS_SDXDPIB_XBASE +
 170                                       (AZX_REG_VS_SDXDPIB_XINTERVAL *
 171                                        hstream->index));
 172        } else {
 173                /*
 174                 * For capture stream, we need more workaround to fix the
 175                 * position incorrect issue:
 176                 *
 177                 * 1. Wait at least 20us before reading position buffer after
 178                 * the interrupt generated(IOC), to make sure position update
 179                 * happens on frame boundary i.e. 20.833uSec for 48KHz.
 180                 * 2. Perform a dummy Read to DPIB register to flush DMA
 181                 * position value.
 182                 * 3. Read the DMA Position from posbuf. Now the readback
 183                 * value should be >= period boundary.
 184                 */
 185                usleep_range(20, 21);
 186                snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
 187                                 AZX_REG_VS_SDXDPIB_XBASE +
 188                                 (AZX_REG_VS_SDXDPIB_XINTERVAL *
 189                                  hstream->index));
 190                pos = snd_hdac_stream_get_pos_posbuf(hstream);
 191        }
 192
 193        if (pos >= hstream->bufsize)
 194                pos = 0;
 195
 196found:
 197        pos = bytes_to_frames(substream->runtime, pos);
 198
 199        dev_vdbg(sdev->dev, "PCM: stream %d dir %d position %lu\n",
 200                 hstream->index, substream->stream, pos);
 201        return pos;
 202}
 203
 204int hda_dsp_pcm_open(struct snd_sof_dev *sdev,
 205                     struct snd_pcm_substream *substream)
 206{
 207        struct hdac_ext_stream *dsp_stream;
 208        int direction = substream->stream;
 209
 210        dsp_stream = hda_dsp_stream_get(sdev, direction);
 211
 212        if (!dsp_stream) {
 213                dev_err(sdev->dev, "error: no stream available\n");
 214                return -ENODEV;
 215        }
 216
 217        /* binding pcm substream to hda stream */
 218        substream->runtime->private_data = &dsp_stream->hstream;
 219        return 0;
 220}
 221
 222int hda_dsp_pcm_close(struct snd_sof_dev *sdev,
 223                      struct snd_pcm_substream *substream)
 224{
 225        struct hdac_stream *hstream = substream->runtime->private_data;
 226        int direction = substream->stream;
 227        int ret;
 228
 229        ret = hda_dsp_stream_put(sdev, direction, hstream->stream_tag);
 230
 231        if (ret) {
 232                dev_dbg(sdev->dev, "stream %s not opened!\n", substream->name);
 233                return -ENODEV;
 234        }
 235
 236        /* unbinding pcm substream to hda stream */
 237        substream->runtime->private_data = NULL;
 238        return 0;
 239}
 240