Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Linux/sound/soc/sti/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 31 kB image not shown  

Quelle  uniperif_player.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) STMicroelectronics SA 2015
 * Authors: Arnaud Pouliquen <arnaud.pouliquen@st.com>
 *          for STMicroelectronics.
 */


#include <linux/clk.h>
#include <linux/mfd/syscon.h>

#include <sound/asoundef.h>
#include <sound/soc.h>

#include "uniperif.h"

/*
 * Some hardware-related definitions
 */


/* sys config registers definitions */
#define SYS_CFG_AUDIO_GLUE 0xA4

/*
 * Driver specific types.
 */


#define UNIPERIF_PLAYER_CLK_ADJ_MIN  -999999
#define UNIPERIF_PLAYER_CLK_ADJ_MAX  1000000
#define UNIPERIF_PLAYER_I2S_OUT 1 /* player id connected to I2S/TDM TX bus */

/*
 * Note: snd_pcm_hardware is linked to DMA controller but is declared here to
 * integrate  DAI_CPU capability in term of rate and supported channels
 */

static const struct snd_pcm_hardware uni_player_pcm_hw = {
 .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
  SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP |
  SNDRV_PCM_INFO_MMAP_VALID,
 .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE,

 .rates = SNDRV_PCM_RATE_CONTINUOUS,
 .rate_min = 8000,
 .rate_max = 192000,

 .channels_min = 2,
 .channels_max = 8,

 .periods_min = 2,
 .periods_max = 48,

 .period_bytes_min = 128,
 .period_bytes_max = 64 * PAGE_SIZE,
 .buffer_bytes_max = 256 * PAGE_SIZE
};

/*
 * uni_player_irq_handler
 * In case of error audio stream is stopped; stop action is protected via PCM
 * stream lock to avoid race condition with trigger callback.
 */

static irqreturn_t uni_player_irq_handler(int irq, void *dev_id)
{
 irqreturn_t ret = IRQ_NONE;
 struct uniperif *player = dev_id;
 unsigned int status;
 unsigned int tmp;

 spin_lock(&player->irq_lock);
 if (!player->substream)
  goto irq_spin_unlock;

 snd_pcm_stream_lock(player->substream);
 if (player->state == UNIPERIF_STATE_STOPPED)
  goto stream_unlock;

 /* Get interrupt status & clear them immediately */
 status = GET_UNIPERIF_ITS(player);
 SET_UNIPERIF_ITS_BCLR(player, status);

 /* Check for fifo error (underrun) */
 if (unlikely(status & UNIPERIF_ITS_FIFO_ERROR_MASK(player))) {
  dev_err(player->dev, "FIFO underflow error detected\n");

  /* Interrupt is just for information when underflow recovery */
  if (player->underflow_enabled) {
   /* Update state to underflow */
   player->state = UNIPERIF_STATE_UNDERFLOW;

  } else {
   /* Disable interrupt so doesn't continually fire */
   SET_UNIPERIF_ITM_BCLR_FIFO_ERROR(player);

   /* Stop the player */
   snd_pcm_stop(player->substream, SNDRV_PCM_STATE_XRUN);
  }

  ret = IRQ_HANDLED;
 }

 /* Check for dma error (overrun) */
 if (unlikely(status & UNIPERIF_ITS_DMA_ERROR_MASK(player))) {
  dev_err(player->dev, "DMA error detected\n");

  /* Disable interrupt so doesn't continually fire */
  SET_UNIPERIF_ITM_BCLR_DMA_ERROR(player);

  /* Stop the player */
  snd_pcm_stop(player->substream, SNDRV_PCM_STATE_XRUN);

  ret = IRQ_HANDLED;
 }

 /* Check for underflow recovery done */
 if (unlikely(status & UNIPERIF_ITM_UNDERFLOW_REC_DONE_MASK(player))) {
  if (!player->underflow_enabled) {
   dev_err(player->dev,
    "unexpected Underflow recovering\n");
   ret = -EPERM;
   goto stream_unlock;
  }
  /* Read the underflow recovery duration */
  tmp = GET_UNIPERIF_STATUS_1_UNDERFLOW_DURATION(player);
  dev_dbg(player->dev, "Underflow recovered (%d LR clocks max)\n",
   tmp);

  /* Clear the underflow recovery duration */
  SET_UNIPERIF_BIT_CONTROL_CLR_UNDERFLOW_DURATION(player);

  /* Update state to started */
  player->state = UNIPERIF_STATE_STARTED;

  ret = IRQ_HANDLED;
 }

 /* Check if underflow recovery failed */
 if (unlikely(status &
       UNIPERIF_ITM_UNDERFLOW_REC_FAILED_MASK(player))) {
  dev_err(player->dev, "Underflow recovery failed\n");

  /* Stop the player */
  snd_pcm_stop(player->substream, SNDRV_PCM_STATE_XRUN);

  ret = IRQ_HANDLED;
 }

stream_unlock:
 snd_pcm_stream_unlock(player->substream);
irq_spin_unlock:
 spin_unlock(&player->irq_lock);

 return ret;
}

static int uni_player_clk_set_rate(struct uniperif *player, unsigned long rate)
{
 int rate_adjusted, rate_achieved, delta, ret;
 int adjustment = player->clk_adj;

 /*
 *             a
 * F = f + --------- * f = f + d
 *          1000000
 *
 *         a
 * d = --------- * f
 *      1000000
 *
 * where:
 *   f - nominal rate
 *   a - adjustment in ppm (parts per milion)
 *   F - rate to be set in synthesizer
 *   d - delta (difference) between f and F
 */

 if (adjustment < 0) {
  /* div64_64 operates on unsigned values... */
  delta = -1;
  adjustment = -adjustment;
 } else {
  delta = 1;
 }
 /* 500000 ppm is 0.5, which is used to round up values */
 delta *= (int)div64_u64((uint64_t)rate *
    (uint64_t)adjustment + 500000, 1000000);
 rate_adjusted = rate + delta;

 /* Adjusted rate should never be == 0 */
 if (!rate_adjusted)
  return -EINVAL;

 ret = clk_set_rate(player->clk, rate_adjusted);
 if (ret < 0)
  return ret;

 rate_achieved = clk_get_rate(player->clk);
 if (!rate_achieved)
  /* If value is 0 means that clock or parent not valid */
  return -EINVAL;

 /*
 * Using ALSA's adjustment control, we can modify the rate to be up
 * to twice as much as requested, but no more
 */

 delta = rate_achieved - rate;
 if (delta < 0) {
  /* div64_64 operates on unsigned values... */
  delta = -delta;
  adjustment = -1;
 } else {
  adjustment = 1;
 }
 /* Frequency/2 is added to round up result */
 adjustment *= (int)div64_u64((uint64_t)delta * 1000000 + rate / 2,
         rate);
 player->clk_adj = adjustment;
 return 0;
}

static void uni_player_set_channel_status(struct uniperif *player,
       struct snd_pcm_runtime *runtime)
{
 int n;
 unsigned int status;

 /*
 * Some AVRs and TVs require the channel status to contain a correct
 * sampling frequency. If no sample rate is already specified, then
 * set one.
 */

 if (runtime) {
  switch (runtime->rate) {
  case 22050:
   player->stream_settings.iec958.status[3] =
      IEC958_AES3_CON_FS_22050;
   break;
  case 44100:
   player->stream_settings.iec958.status[3] =
      IEC958_AES3_CON_FS_44100;
   break;
  case 88200:
   player->stream_settings.iec958.status[3] =
      IEC958_AES3_CON_FS_88200;
   break;
  case 176400:
   player->stream_settings.iec958.status[3] =
      IEC958_AES3_CON_FS_176400;
   break;
  case 24000:
   player->stream_settings.iec958.status[3] =
      IEC958_AES3_CON_FS_24000;
   break;
  case 48000:
   player->stream_settings.iec958.status[3] =
      IEC958_AES3_CON_FS_48000;
   break;
  case 96000:
   player->stream_settings.iec958.status[3] =
      IEC958_AES3_CON_FS_96000;
   break;
  case 192000:
   player->stream_settings.iec958.status[3] =
      IEC958_AES3_CON_FS_192000;
   break;
  case 32000:
   player->stream_settings.iec958.status[3] =
      IEC958_AES3_CON_FS_32000;
   break;
  default:
   /* Mark as sampling frequency not indicated */
   player->stream_settings.iec958.status[3] =
      IEC958_AES3_CON_FS_NOTID;
   break;
  }
 }

 /* Audio mode:
 * Use audio mode status to select PCM or encoded mode
 */

 if (player->stream_settings.iec958.status[0] & IEC958_AES0_NONAUDIO)
  player->stream_settings.encoding_mode =
   UNIPERIF_IEC958_ENCODING_MODE_ENCODED;
 else
  player->stream_settings.encoding_mode =
   UNIPERIF_IEC958_ENCODING_MODE_PCM;

 if (player->stream_settings.encoding_mode ==
  UNIPERIF_IEC958_ENCODING_MODE_PCM)
  /* Clear user validity bits */
  SET_UNIPERIF_USER_VALIDITY_VALIDITY_LR(player, 0);
 else
  /* Set user validity bits */
  SET_UNIPERIF_USER_VALIDITY_VALIDITY_LR(player, 1);

 /* Program the new channel status */
 for (n = 0; n < 6; ++n) {
  status  =
  player->stream_settings.iec958.status[0 + (n * 4)] & 0xf;
  status |=
  player->stream_settings.iec958.status[1 + (n * 4)] << 8;
  status |=
  player->stream_settings.iec958.status[2 + (n * 4)] << 16;
  status |=
  player->stream_settings.iec958.status[3 + (n * 4)] << 24;
  SET_UNIPERIF_CHANNEL_STA_REGN(player, n, status);
 }

 /* Update the channel status */
 if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0)
  SET_UNIPERIF_CONFIG_CHL_STS_UPDATE(player);
 else
  SET_UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE(player);
}

static int uni_player_prepare_iec958(struct uniperif *player,
         struct snd_pcm_runtime *runtime)
{
 int clk_div;

 clk_div = player->mclk / runtime->rate;

 /* Oversampling must be multiple of 128 as iec958 frame is 32-bits */
 if ((clk_div % 128) || (clk_div <= 0)) {
  dev_err(player->dev, "%s: invalid clk_div %d\n",
   __func__, clk_div);
  return -EINVAL;
 }

 switch (runtime->format) {
 case SNDRV_PCM_FORMAT_S16_LE:
  /* 16/16 memory format */
  SET_UNIPERIF_CONFIG_MEM_FMT_16_16(player);
  /* 16-bits per sub-frame */
  SET_UNIPERIF_I2S_FMT_NBIT_32(player);
  /* Set 16-bit sample precision */
  SET_UNIPERIF_I2S_FMT_DATA_SIZE_16(player);
  break;
 case SNDRV_PCM_FORMAT_S32_LE:
  /* 16/0 memory format */
  SET_UNIPERIF_CONFIG_MEM_FMT_16_0(player);
  /* 32-bits per sub-frame */
  SET_UNIPERIF_I2S_FMT_NBIT_32(player);
  /* Set 24-bit sample precision */
  SET_UNIPERIF_I2S_FMT_DATA_SIZE_24(player);
  break;
 default:
  dev_err(player->dev, "format not supported\n");
  return -EINVAL;
 }

 /* Set parity to be calculated by the hardware */
 SET_UNIPERIF_CONFIG_PARITY_CNTR_BY_HW(player);

 /* Set channel status bits to be inserted by the hardware */
 SET_UNIPERIF_CONFIG_CHANNEL_STA_CNTR_BY_HW(player);

 /* Set user data bits to be inserted by the hardware */
 SET_UNIPERIF_CONFIG_USER_DAT_CNTR_BY_HW(player);

 /* Set validity bits to be inserted by the hardware */
 SET_UNIPERIF_CONFIG_VALIDITY_DAT_CNTR_BY_HW(player);

 /* Set full software control to disabled */
 SET_UNIPERIF_CONFIG_SPDIF_SW_CTRL_DISABLE(player);

 SET_UNIPERIF_CTRL_ZERO_STUFF_HW(player);

 mutex_lock(&player->ctrl_lock);
 /* Update the channel status */
 uni_player_set_channel_status(player, runtime);
 mutex_unlock(&player->ctrl_lock);

 /* Clear the user validity user bits */
 SET_UNIPERIF_USER_VALIDITY_VALIDITY_LR(player, 0);

 /* Disable one-bit audio mode */
 SET_UNIPERIF_CONFIG_ONE_BIT_AUD_DISABLE(player);

 /* Enable consecutive frames repetition of Z preamble (not for HBRA) */
 SET_UNIPERIF_CONFIG_REPEAT_CHL_STS_ENABLE(player);

 /* Change to SUF0_SUBF1 and left/right channels swap! */
 SET_UNIPERIF_CONFIG_SUBFRAME_SEL_SUBF1_SUBF0(player);

 /* Set data output as MSB first */
 SET_UNIPERIF_I2S_FMT_ORDER_MSB(player);

 if (player->stream_settings.encoding_mode ==
    UNIPERIF_IEC958_ENCODING_MODE_ENCODED)
  SET_UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_ON(player);
 else
  SET_UNIPERIF_CTRL_EXIT_STBY_ON_EOBLOCK_OFF(player);

 SET_UNIPERIF_I2S_FMT_NUM_CH(player, runtime->channels / 2);

 /* Set rounding to off */
 SET_UNIPERIF_CTRL_ROUNDING_OFF(player);

 /* Set clock divisor */
 SET_UNIPERIF_CTRL_DIVIDER(player, clk_div / 128);

 /* Set the spdif latency to not wait before starting player */
 SET_UNIPERIF_CTRL_SPDIF_LAT_OFF(player);

 /*
 * Ensure iec958 formatting is off. It will be enabled in function
 * uni_player_start() at the same time as the operation
 * mode is set to work around a silicon issue.
 */

 if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0)
  SET_UNIPERIF_CTRL_SPDIF_FMT_OFF(player);
 else
  SET_UNIPERIF_CTRL_SPDIF_FMT_ON(player);

 return 0;
}

static int uni_player_prepare_pcm(struct uniperif *player,
      struct snd_pcm_runtime *runtime)
{
 int output_frame_size, slot_width, clk_div;

 /* Force slot width to 32 in I2S mode (HW constraint) */
 if ((player->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) ==
  SND_SOC_DAIFMT_I2S)
  slot_width = 32;
 else
  slot_width = snd_pcm_format_width(runtime->format);

 output_frame_size = slot_width * runtime->channels;

 clk_div = player->mclk / runtime->rate;
 /*
 * For 32 bits subframe clk_div must be a multiple of 128,
 * for 16 bits must be a multiple of 64
 */

 if ((slot_width == 32) && (clk_div % 128)) {
  dev_err(player->dev, "%s: invalid clk_div\n", __func__);
  return -EINVAL;
 }

 if ((slot_width == 16) && (clk_div % 64)) {
  dev_err(player->dev, "%s: invalid clk_div\n", __func__);
  return -EINVAL;
 }

 /*
 * Number of bits per subframe (which is one channel sample)
 * on output - Transfer 16 or 32 bits from FIFO
 */

 switch (slot_width) {
 case 32:
  SET_UNIPERIF_I2S_FMT_NBIT_32(player);
  SET_UNIPERIF_I2S_FMT_DATA_SIZE_32(player);
  break;
 case 16:
  SET_UNIPERIF_I2S_FMT_NBIT_16(player);
  SET_UNIPERIF_I2S_FMT_DATA_SIZE_16(player);
  break;
 default:
  dev_err(player->dev, "subframe format not supported\n");
  return -EINVAL;
 }

 /* Configure data memory format */
 switch (runtime->format) {
 case SNDRV_PCM_FORMAT_S16_LE:
  /* One data word contains two samples */
  SET_UNIPERIF_CONFIG_MEM_FMT_16_16(player);
  break;

 case SNDRV_PCM_FORMAT_S32_LE:
  /*
 * Actually "16 bits/0 bits" means "32/28/24/20/18/16 bits
 * on the left than zeros (if less than 32 bytes)"... ;-)
 */

  SET_UNIPERIF_CONFIG_MEM_FMT_16_0(player);
  break;

 default:
  dev_err(player->dev, "format not supported\n");
  return -EINVAL;
 }

 /* Set rounding to off */
 SET_UNIPERIF_CTRL_ROUNDING_OFF(player);

 /* Set clock divisor */
 SET_UNIPERIF_CTRL_DIVIDER(player, clk_div / (2 * output_frame_size));

 /* Number of channelsmust be even*/
 if ((runtime->channels % 2) || (runtime->channels < 2) ||
     (runtime->channels > 10)) {
  dev_err(player->dev, "%s: invalid nb of channels\n", __func__);
  return -EINVAL;
 }

 SET_UNIPERIF_I2S_FMT_NUM_CH(player, runtime->channels / 2);

 /* Set 1-bit audio format to disabled */
 SET_UNIPERIF_CONFIG_ONE_BIT_AUD_DISABLE(player);

 SET_UNIPERIF_I2S_FMT_ORDER_MSB(player);

 /* No iec958 formatting as outputting to DAC  */
 SET_UNIPERIF_CTRL_SPDIF_FMT_OFF(player);

 return 0;
}

static int uni_player_prepare_tdm(struct uniperif *player,
      struct snd_pcm_runtime *runtime)
{
 int tdm_frame_size; /* unip tdm frame size in bytes */
 int user_frame_size; /* user tdm frame size in bytes */
 /* default unip TDM_WORD_POS_X_Y */
 unsigned int word_pos[4] = {
  0x04060002, 0x0C0E080A, 0x14161012, 0x1C1E181A};
 int freq, ret;

 tdm_frame_size =
  sti_uniperiph_get_unip_tdm_frame_size(player);
 user_frame_size =
  sti_uniperiph_get_user_frame_size(runtime);

 /* fix 16/0 format */
 SET_UNIPERIF_CONFIG_MEM_FMT_16_0(player);
 SET_UNIPERIF_I2S_FMT_DATA_SIZE_32(player);

 /* number of words inserted on the TDM line */
 SET_UNIPERIF_I2S_FMT_NUM_CH(player, user_frame_size / 4 / 2);

 SET_UNIPERIF_I2S_FMT_ORDER_MSB(player);
 SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(player);

 /* Enable the tdm functionality */
 SET_UNIPERIF_TDM_ENABLE_TDM_ENABLE(player);

 /* number of 8 bits timeslots avail in unip tdm frame */
 SET_UNIPERIF_TDM_FS_REF_DIV_NUM_TIMESLOT(player, tdm_frame_size);

 /* set the timeslot allocation for words in FIFO */
 sti_uniperiph_get_tdm_word_pos(player, word_pos);
 SET_UNIPERIF_TDM_WORD_POS(player, 1_2, word_pos[WORD_1_2]);
 SET_UNIPERIF_TDM_WORD_POS(player, 3_4, word_pos[WORD_3_4]);
 SET_UNIPERIF_TDM_WORD_POS(player, 5_6, word_pos[WORD_5_6]);
 SET_UNIPERIF_TDM_WORD_POS(player, 7_8, word_pos[WORD_7_8]);

 /* set unip clk rate (not done vai set_sysclk ops) */
 freq = runtime->rate * tdm_frame_size * 8;
 mutex_lock(&player->ctrl_lock);
 ret = uni_player_clk_set_rate(player, freq);
 if (!ret)
  player->mclk = freq;
 mutex_unlock(&player->ctrl_lock);

 return 0;
}

/*
 * ALSA uniperipheral iec958 controls
 */

static int  uni_player_ctl_iec958_info(struct snd_kcontrol *kcontrol,
           struct snd_ctl_elem_info *uinfo)
{
 uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
 uinfo->count = 1;

 return 0;
}

static int uni_player_ctl_iec958_get(struct snd_kcontrol *kcontrol,
         struct snd_ctl_elem_value *ucontrol)
{
 struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
 struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
 struct uniperif *player = priv->dai_data.uni;
 struct snd_aes_iec958 *iec958 = &player->stream_settings.iec958;

 mutex_lock(&player->ctrl_lock);
 ucontrol->value.iec958.status[0] = iec958->status[0];
 ucontrol->value.iec958.status[1] = iec958->status[1];
 ucontrol->value.iec958.status[2] = iec958->status[2];
 ucontrol->value.iec958.status[3] = iec958->status[3];
 mutex_unlock(&player->ctrl_lock);
 return 0;
}

static int uni_player_ctl_iec958_put(struct snd_kcontrol *kcontrol,
         struct snd_ctl_elem_value *ucontrol)
{
 struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
 struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
 struct uniperif *player = priv->dai_data.uni;
 struct snd_aes_iec958 *iec958 =  &player->stream_settings.iec958;
 unsigned long flags;

 mutex_lock(&player->ctrl_lock);
 iec958->status[0] = ucontrol->value.iec958.status[0];
 iec958->status[1] = ucontrol->value.iec958.status[1];
 iec958->status[2] = ucontrol->value.iec958.status[2];
 iec958->status[3] = ucontrol->value.iec958.status[3];

 spin_lock_irqsave(&player->irq_lock, flags);
 if (player->substream && player->substream->runtime)
  uni_player_set_channel_status(player,
           player->substream->runtime);
 else
  uni_player_set_channel_status(player, NULL);

 spin_unlock_irqrestore(&player->irq_lock, flags);
 mutex_unlock(&player->ctrl_lock);

 return 0;
}

static struct snd_kcontrol_new uni_player_iec958_ctl = {
 .iface = SNDRV_CTL_ELEM_IFACE_PCM,
 .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
 .info = uni_player_ctl_iec958_info,
 .get = uni_player_ctl_iec958_get,
 .put = uni_player_ctl_iec958_put,
};

/*
 * uniperif rate adjustement control
 */

static int snd_sti_clk_adjustment_info(struct snd_kcontrol *kcontrol,
           struct snd_ctl_elem_info *uinfo)
{
 uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
 uinfo->count = 1;
 uinfo->value.integer.min = UNIPERIF_PLAYER_CLK_ADJ_MIN;
 uinfo->value.integer.max = UNIPERIF_PLAYER_CLK_ADJ_MAX;
 uinfo->value.integer.step = 1;

 return 0;
}

static int snd_sti_clk_adjustment_get(struct snd_kcontrol *kcontrol,
          struct snd_ctl_elem_value *ucontrol)
{
 struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
 struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
 struct uniperif *player = priv->dai_data.uni;

 mutex_lock(&player->ctrl_lock);
 ucontrol->value.integer.value[0] = player->clk_adj;
 mutex_unlock(&player->ctrl_lock);

 return 0;
}

static int snd_sti_clk_adjustment_put(struct snd_kcontrol *kcontrol,
          struct snd_ctl_elem_value *ucontrol)
{
 struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
 struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
 struct uniperif *player = priv->dai_data.uni;
 int ret = 0;

 if ((ucontrol->value.integer.value[0] < UNIPERIF_PLAYER_CLK_ADJ_MIN) ||
     (ucontrol->value.integer.value[0] > UNIPERIF_PLAYER_CLK_ADJ_MAX))
  return -EINVAL;

 mutex_lock(&player->ctrl_lock);
 player->clk_adj = ucontrol->value.integer.value[0];

 if (player->mclk)
  ret = uni_player_clk_set_rate(player, player->mclk);
 mutex_unlock(&player->ctrl_lock);

 return ret;
}

static struct snd_kcontrol_new uni_player_clk_adj_ctl = {
 .iface = SNDRV_CTL_ELEM_IFACE_PCM,
 .name = "PCM Playback Oversampling Freq. Adjustment",
 .info = snd_sti_clk_adjustment_info,
 .get = snd_sti_clk_adjustment_get,
 .put = snd_sti_clk_adjustment_put,
};

static struct snd_kcontrol_new *snd_sti_pcm_ctl[] = {
 &uni_player_clk_adj_ctl,
};

static struct snd_kcontrol_new *snd_sti_iec_ctl[] = {
 &uni_player_iec958_ctl,
 &uni_player_clk_adj_ctl,
};

static int uni_player_startup(struct snd_pcm_substream *substream,
         struct snd_soc_dai *dai)
{
 struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
 struct uniperif *player = priv->dai_data.uni;
 unsigned long flags;
 int ret;

 spin_lock_irqsave(&player->irq_lock, flags);
 player->substream = substream;
 spin_unlock_irqrestore(&player->irq_lock, flags);

 player->clk_adj = 0;

 if (!UNIPERIF_TYPE_IS_TDM(player))
  return 0;

 /* refine hw constraint in tdm mode */
 ret = snd_pcm_hw_rule_add(substream->runtime, 0,
      SNDRV_PCM_HW_PARAM_CHANNELS,
      sti_uniperiph_fix_tdm_chan,
      player, SNDRV_PCM_HW_PARAM_CHANNELS,
      -1);
 if (ret < 0)
  return ret;

 return snd_pcm_hw_rule_add(substream->runtime, 0,
       SNDRV_PCM_HW_PARAM_FORMAT,
       sti_uniperiph_fix_tdm_format,
       player, SNDRV_PCM_HW_PARAM_FORMAT,
       -1);
}

static int uni_player_set_sysclk(struct snd_soc_dai *dai, int clk_id,
     unsigned int freq, int dir)
{
 struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
 struct uniperif *player = priv->dai_data.uni;
 int ret;

 if (UNIPERIF_TYPE_IS_TDM(player) || (dir == SND_SOC_CLOCK_IN))
  return 0;

 if (clk_id != 0)
  return -EINVAL;

 mutex_lock(&player->ctrl_lock);
 ret = uni_player_clk_set_rate(player, freq);
 if (!ret)
  player->mclk = freq;
 mutex_unlock(&player->ctrl_lock);

 return ret;
}

static int uni_player_prepare(struct snd_pcm_substream *substream,
         struct snd_soc_dai *dai)
{
 struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
 struct uniperif *player = priv->dai_data.uni;
 struct snd_pcm_runtime *runtime = substream->runtime;
 int transfer_size, trigger_limit;
 int ret;

 /* The player should be stopped */
 if (player->state != UNIPERIF_STATE_STOPPED) {
  dev_err(player->dev, "%s: invalid player state %d\n", __func__,
   player->state);
  return -EINVAL;
 }

 /* Calculate transfer size (in fifo cells and bytes) for frame count */
 if (player->type == SND_ST_UNIPERIF_TYPE_TDM) {
  /* transfer size = user frame size (in 32 bits FIFO cell) */
  transfer_size =
   sti_uniperiph_get_user_frame_size(runtime) / 4;
 } else {
  transfer_size = runtime->channels * UNIPERIF_FIFO_FRAMES;
 }

 /* Calculate number of empty cells available before asserting DREQ */
 if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0) {
  trigger_limit = UNIPERIF_FIFO_SIZE - transfer_size;
 } else {
  /*
 * Since SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0
 * FDMA_TRIGGER_LIMIT also controls when the state switches
 * from OFF or STANDBY to AUDIO DATA.
 */

  trigger_limit = transfer_size;
 }

 /* Trigger limit must be an even number */
 if ((!trigger_limit % 2) || (trigger_limit != 1 && transfer_size % 2) ||
     (trigger_limit > UNIPERIF_CONFIG_DMA_TRIG_LIMIT_MASK(player))) {
  dev_err(player->dev, "invalid trigger limit %d\n",
   trigger_limit);
  return -EINVAL;
 }

 SET_UNIPERIF_CONFIG_DMA_TRIG_LIMIT(player, trigger_limit);

 /* Uniperipheral setup depends on player type */
 switch (player->type) {
 case SND_ST_UNIPERIF_TYPE_HDMI:
  ret = uni_player_prepare_iec958(player, runtime);
  break;
 case SND_ST_UNIPERIF_TYPE_PCM:
  ret = uni_player_prepare_pcm(player, runtime);
  break;
 case SND_ST_UNIPERIF_TYPE_SPDIF:
  ret = uni_player_prepare_iec958(player, runtime);
  break;
 case SND_ST_UNIPERIF_TYPE_TDM:
  ret = uni_player_prepare_tdm(player, runtime);
  break;
 default:
  dev_err(player->dev, "invalid player type\n");
  return -EINVAL;
 }

 if (ret)
  return ret;

 switch (player->daifmt & SND_SOC_DAIFMT_INV_MASK) {
 case SND_SOC_DAIFMT_NB_NF:
  SET_UNIPERIF_I2S_FMT_LR_POL_LOW(player);
  SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(player);
  break;
 case SND_SOC_DAIFMT_NB_IF:
  SET_UNIPERIF_I2S_FMT_LR_POL_HIG(player);
  SET_UNIPERIF_I2S_FMT_SCLK_EDGE_RISING(player);
  break;
 case SND_SOC_DAIFMT_IB_NF:
  SET_UNIPERIF_I2S_FMT_LR_POL_LOW(player);
  SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(player);
  break;
 case SND_SOC_DAIFMT_IB_IF:
  SET_UNIPERIF_I2S_FMT_LR_POL_HIG(player);
  SET_UNIPERIF_I2S_FMT_SCLK_EDGE_FALLING(player);
  break;
 }

 switch (player->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) {
 case SND_SOC_DAIFMT_I2S:
  SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(player);
  SET_UNIPERIF_I2S_FMT_PADDING_I2S_MODE(player);
  break;
 case SND_SOC_DAIFMT_LEFT_J:
  SET_UNIPERIF_I2S_FMT_ALIGN_LEFT(player);
  SET_UNIPERIF_I2S_FMT_PADDING_SONY_MODE(player);
  break;
 case SND_SOC_DAIFMT_RIGHT_J:
  SET_UNIPERIF_I2S_FMT_ALIGN_RIGHT(player);
  SET_UNIPERIF_I2S_FMT_PADDING_SONY_MODE(player);
  break;
 default:
  dev_err(player->dev, "format not supported\n");
  return -EINVAL;
 }

 SET_UNIPERIF_I2S_FMT_NO_OF_SAMPLES_TO_READ(player, 0);


 return sti_uniperiph_reset(player);
}

static int uni_player_start(struct uniperif *player)
{
 int ret;

 /* The player should be stopped */
 if (player->state != UNIPERIF_STATE_STOPPED) {
  dev_err(player->dev, "%s: invalid player state\n", __func__);
  return -EINVAL;
 }

 ret = clk_prepare_enable(player->clk);
 if (ret) {
  dev_err(player->dev, "%s: Failed to enable clock\n", __func__);
  return ret;
 }

 /* Clear any pending interrupts */
 SET_UNIPERIF_ITS_BCLR(player, GET_UNIPERIF_ITS(player));

 /* Set the interrupt mask */
 SET_UNIPERIF_ITM_BSET_DMA_ERROR(player);
 SET_UNIPERIF_ITM_BSET_FIFO_ERROR(player);

 /* Enable underflow recovery interrupts */
 if (player->underflow_enabled) {
  SET_UNIPERIF_ITM_BSET_UNDERFLOW_REC_DONE(player);
  SET_UNIPERIF_ITM_BSET_UNDERFLOW_REC_FAILED(player);
 }

 ret = sti_uniperiph_reset(player);
 if (ret < 0) {
  clk_disable_unprepare(player->clk);
  return ret;
 }

 /*
 * Does not use IEC61937 features of the uniperipheral hardware.
 * Instead it performs IEC61937 in software and inserts it directly
 * into the audio data stream. As such, when encoded mode is selected,
 * linear pcm mode is still used, but with the differences of the
 * channel status bits set for encoded mode and the validity bits set.
 */

 SET_UNIPERIF_CTRL_OPERATION_PCM_DATA(player);

 /*
 * If iec958 formatting is required for hdmi or spdif, then it must be
 * enabled after the operation mode is set. If set prior to this, it
 * will not take affect and hang the player.
 */

 if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0)
  if (UNIPERIF_TYPE_IS_IEC958(player))
   SET_UNIPERIF_CTRL_SPDIF_FMT_ON(player);

 /* Force channel status update (no update if clk disable) */
 if (player->ver < SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0)
  SET_UNIPERIF_CONFIG_CHL_STS_UPDATE(player);
 else
  SET_UNIPERIF_BIT_CONTROL_CHL_STS_UPDATE(player);

 /* Update state to started */
 player->state = UNIPERIF_STATE_STARTED;

 return 0;
}

static int uni_player_stop(struct uniperif *player)
{
 int ret;

 /* The player should not be in stopped state */
 if (player->state == UNIPERIF_STATE_STOPPED) {
  dev_err(player->dev, "%s: invalid player state\n", __func__);
  return -EINVAL;
 }

 /* Turn the player off */
 SET_UNIPERIF_CTRL_OPERATION_OFF(player);

 ret = sti_uniperiph_reset(player);
 if (ret < 0)
  return ret;

 /* Disable interrupts */
 SET_UNIPERIF_ITM_BCLR(player, GET_UNIPERIF_ITM(player));

 /* Disable clock */
 clk_disable_unprepare(player->clk);

 /* Update state to stopped and return */
 player->state = UNIPERIF_STATE_STOPPED;

 return 0;
}

int uni_player_resume(struct uniperif *player)
{
 int ret;

 /* Select the frequency synthesizer clock */
 if (player->clk_sel) {
  ret = regmap_field_write(player->clk_sel, 1);
  if (ret) {
   dev_err(player->dev,
    "%s: Failed to select freq synth clock\n",
    __func__);
   return ret;
  }
 }

 SET_UNIPERIF_CONFIG_BACK_STALL_REQ_DISABLE(player);
 SET_UNIPERIF_CTRL_ROUNDING_OFF(player);
 SET_UNIPERIF_CTRL_SPDIF_LAT_OFF(player);
 SET_UNIPERIF_CONFIG_IDLE_MOD_DISABLE(player);

 return 0;
}
EXPORT_SYMBOL_GPL(uni_player_resume);

static int uni_player_trigger(struct snd_pcm_substream *substream,
         int cmd, struct snd_soc_dai *dai)
{
 struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
 struct uniperif *player = priv->dai_data.uni;

 switch (cmd) {
 case SNDRV_PCM_TRIGGER_START:
  return uni_player_start(player);
 case SNDRV_PCM_TRIGGER_STOP:
  return uni_player_stop(player);
 case SNDRV_PCM_TRIGGER_RESUME:
  return uni_player_resume(player);
 default:
  return -EINVAL;
 }
}

static void uni_player_shutdown(struct snd_pcm_substream *substream,
    struct snd_soc_dai *dai)
{
 struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
 struct uniperif *player = priv->dai_data.uni;
 unsigned long flags;

 spin_lock_irqsave(&player->irq_lock, flags);
 if (player->state != UNIPERIF_STATE_STOPPED)
  /* Stop the player */
  uni_player_stop(player);

 player->substream = NULL;
 spin_unlock_irqrestore(&player->irq_lock, flags);
}

static int uni_player_parse_dt_audio_glue(struct platform_device *pdev,
       struct uniperif *player)
{
 struct device_node *node = pdev->dev.of_node;
 struct regmap *regmap;
 struct reg_field regfield[2] = {
  /* PCM_CLK_SEL */
  REG_FIELD(SYS_CFG_AUDIO_GLUE,
     8 + player->id,
     8 + player->id),
  /* PCMP_VALID_SEL */
  REG_FIELD(SYS_CFG_AUDIO_GLUE, 0, 1)
 };

 regmap = syscon_regmap_lookup_by_phandle(node, "st,syscfg");

 if (IS_ERR(regmap)) {
  dev_err(&pdev->dev, "sti-audio-clk-glue syscf not found\n");
  return PTR_ERR(regmap);
 }

 player->clk_sel = regmap_field_alloc(regmap, regfield[0]);
 player->valid_sel = regmap_field_alloc(regmap, regfield[1]);

 return 0;
}

static const struct snd_soc_dai_ops uni_player_dai_ops = {
  .startup = uni_player_startup,
  .shutdown = uni_player_shutdown,
  .prepare = uni_player_prepare,
  .probe = sti_uniperiph_dai_probe,
  .trigger = uni_player_trigger,
  .hw_params = sti_uniperiph_dai_hw_params,
  .set_fmt = sti_uniperiph_dai_set_fmt,
  .set_sysclk = uni_player_set_sysclk,
  .set_tdm_slot = sti_uniperiph_set_tdm_slot
};

int uni_player_init(struct platform_device *pdev,
      struct uniperif *player)
{
 int ret = 0;

 player->dev = &pdev->dev;
 player->state = UNIPERIF_STATE_STOPPED;
 player->dai_ops = &uni_player_dai_ops;

 /* Get PCM_CLK_SEL & PCMP_VALID_SEL from audio-glue-ctrl SoC reg */
 ret = uni_player_parse_dt_audio_glue(pdev, player);

 if (ret < 0) {
  dev_err(player->dev, "Failed to parse DeviceTree\n");
  return ret;
 }

 /* Underflow recovery is only supported on later ip revisions */
 if (player->ver >= SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0)
  player->underflow_enabled = 1;

 if (UNIPERIF_TYPE_IS_TDM(player))
  player->hw = &uni_tdm_hw;
 else
  player->hw = &uni_player_pcm_hw;

 /* Get uniperif resource */
 player->clk = of_clk_get(pdev->dev.of_node, 0);
 if (IS_ERR(player->clk)) {
  dev_err(player->dev, "Failed to get clock\n");
  return PTR_ERR(player->clk);
 }

 /* Select the frequency synthesizer clock */
 if (player->clk_sel) {
  ret = regmap_field_write(player->clk_sel, 1);
  if (ret) {
   dev_err(player->dev,
    "%s: Failed to select freq synth clock\n",
    __func__);
   return ret;
  }
 }

 /* connect to I2S/TDM TX bus */
 if (player->valid_sel &&
     (player->id == UNIPERIF_PLAYER_I2S_OUT)) {
  ret = regmap_field_write(player->valid_sel, player->id);
  if (ret) {
   dev_err(player->dev,
    "%s: unable to connect to tdm bus\n", __func__);
   return ret;
  }
 }

 ret = devm_request_irq(&pdev->dev, player->irq,
          uni_player_irq_handler, IRQF_SHARED,
          dev_name(&pdev->dev), player);
 if (ret < 0) {
  dev_err(player->dev, "unable to request IRQ %d\n", player->irq);
  return ret;
 }

 mutex_init(&player->ctrl_lock);
 spin_lock_init(&player->irq_lock);

 /* Ensure that disabled by default */
 SET_UNIPERIF_CONFIG_BACK_STALL_REQ_DISABLE(player);
 SET_UNIPERIF_CTRL_ROUNDING_OFF(player);
 SET_UNIPERIF_CTRL_SPDIF_LAT_OFF(player);
 SET_UNIPERIF_CONFIG_IDLE_MOD_DISABLE(player);

 if (UNIPERIF_TYPE_IS_IEC958(player)) {
  /* Set default iec958 status bits  */

  /* Consumer, PCM, copyright, 2ch, mode 0 */
  player->stream_settings.iec958.status[0] = 0x00;
  /* Broadcast reception category */
  player->stream_settings.iec958.status[1] =
     IEC958_AES1_CON_GENERAL;
  /* Do not take into account source or channel number */
  player->stream_settings.iec958.status[2] =
     IEC958_AES2_CON_SOURCE_UNSPEC;
  /* Sampling frequency not indicated */
  player->stream_settings.iec958.status[3] =
     IEC958_AES3_CON_FS_NOTID;
  /* Max sample word 24-bit, sample word length not indicated */
  player->stream_settings.iec958.status[4] =
     IEC958_AES4_CON_MAX_WORDLEN_24 |
     IEC958_AES4_CON_WORDLEN_24_20;

  player->num_ctrls = ARRAY_SIZE(snd_sti_iec_ctl);
  player->snd_ctrls = snd_sti_iec_ctl[0];
 } else {
  player->num_ctrls = ARRAY_SIZE(snd_sti_pcm_ctl);
  player->snd_ctrls = snd_sti_pcm_ctl[0];
 }

 return 0;
}
EXPORT_SYMBOL_GPL(uni_player_init);

Messung V0.5
C=90 H=98 G=94

¤ Dauer der Verarbeitung: 0.3 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.