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

Quelle  mchp-spdiftx.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
//
// Driver for Microchip S/PDIF TX Controller
//
// Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
//
// Author: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>

#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/spinlock.h>

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

/*
 * ---- S/PDIF Transmitter Controller Register map ----
 */

#define SPDIFTX_CR   0x00 /* Control Register */
#define SPDIFTX_MR   0x04 /* Mode Register */
#define SPDIFTX_CDR   0x0C /* Common Data Register */

#define SPDIFTX_IER   0x14 /* Interrupt Enable Register */
#define SPDIFTX_IDR   0x18 /* Interrupt Disable Register */
#define SPDIFTX_IMR   0x1C /* Interrupt Mask Register */
#define SPDIFTX_ISR   0x20 /* Interrupt Status Register */

#define SPDIFTX_CH1UD(reg) (0x50 + (reg) * 4) /* User Data 1 Register x */
#define SPDIFTX_CH1S(reg) (0x80 + (reg) * 4) /* Channel Status 1 Register x */

#define SPDIFTX_VERSION   0xF0

/*
 * ---- Control Register (Write-only) ----
 */

#define SPDIFTX_CR_SWRST  BIT(0) /* Software Reset */
#define SPDIFTX_CR_FCLR   BIT(1) /* FIFO clear */

/*
 * ---- Mode Register (Read/Write) ----
 */

/* Transmit Enable */
#define SPDIFTX_MR_TXEN_MASK  GENMASK(0, 0)
#define SPDIFTX_MR_TXEN_DISABLE  (0 << 0)
#define SPDIFTX_MR_TXEN_ENABLE  (1 << 0)

/* Multichannel Transfer */
#define SPDIFTX_MR_MULTICH_MASK  GENAMSK(1, 1)
#define SPDIFTX_MR_MULTICH_MONO  (0 << 1)
#define SPDIFTX_MR_MULTICH_DUAL  (1 << 1)

/* Data Word Endian Mode */
#define SPDIFTX_MR_ENDIAN_MASK  GENMASK(2, 2)
#define SPDIFTX_MR_ENDIAN_LITTLE (0 << 2)
#define SPDIFTX_MR_ENDIAN_BIG  (1 << 2)

/* Data Justification */
#define SPDIFTX_MR_JUSTIFY_MASK  GENMASK(3, 3)
#define SPDIFTX_MR_JUSTIFY_LSB  (0 << 3)
#define SPDIFTX_MR_JUSTIFY_MSB  (1 << 3)

/* Common Audio Register Transfer Mode */
#define SPDIFTX_MR_CMODE_MASK   GENMASK(5, 4)
#define SPDIFTX_MR_CMODE_INDEX_ACCESS  (0 << 4)
#define SPDIFTX_MR_CMODE_TOGGLE_ACCESS  (1 << 4)
#define SPDIFTX_MR_CMODE_INTERLVD_ACCESS (2 << 4)

/* Valid Bits per Sample */
#define SPDIFTX_MR_VBPS_MASK  GENMASK(13, 8)

/* Chunk Size */
#define SPDIFTX_MR_CHUNK_MASK  GENMASK(19, 16)

/* Validity Bits for Channels 1 and 2 */
#define SPDIFTX_MR_VALID1   BIT(24)
#define SPDIFTX_MR_VALID2   BIT(25)

/* Disable Null Frame on underrun */
#define SPDIFTX_MR_DNFR_MASK  GENMASK(27, 27)
#define SPDIFTX_MR_DNFR_INVALID  (0 << 27)
#define SPDIFTX_MR_DNFR_VALID  (1 << 27)

/* Bytes per Sample */
#define SPDIFTX_MR_BPS_MASK  GENMASK(29, 28)

/*
 * ---- Interrupt Enable/Disable/Mask/Status Register (Write/Read-only) ----
 */

#define SPDIFTX_IR_TXRDY  BIT(0)
#define SPDIFTX_IR_TXEMPTY  BIT(1)
#define SPDIFTX_IR_TXFULL  BIT(2)
#define SPDIFTX_IR_TXCHUNK  BIT(3)
#define SPDIFTX_IR_TXUDR  BIT(4)
#define SPDIFTX_IR_TXOVR  BIT(5)
#define SPDIFTX_IR_CSRDY  BIT(6)
#define SPDIFTX_IR_UDRDY  BIT(7)
#define SPDIFTX_IR_TXRDYCH(ch)  BIT((ch) + 8)
#define SPDIFTX_IR_SECE   BIT(10)
#define SPDIFTX_IR_TXUDRCH(ch)  BIT((ch) + 11)
#define SPDIFTX_IR_BEND   BIT(13)

static bool mchp_spdiftx_readable_reg(struct device *dev, unsigned int reg)
{
 switch (reg) {
 case SPDIFTX_MR:
 case SPDIFTX_IMR:
 case SPDIFTX_ISR:
 case SPDIFTX_CH1UD(0):
 case SPDIFTX_CH1UD(1):
 case SPDIFTX_CH1UD(2):
 case SPDIFTX_CH1UD(3):
 case SPDIFTX_CH1UD(4):
 case SPDIFTX_CH1UD(5):
 case SPDIFTX_CH1S(0):
 case SPDIFTX_CH1S(1):
 case SPDIFTX_CH1S(2):
 case SPDIFTX_CH1S(3):
 case SPDIFTX_CH1S(4):
 case SPDIFTX_CH1S(5):
  return true;
 default:
  return false;
 }
}

static bool mchp_spdiftx_writeable_reg(struct device *dev, unsigned int reg)
{
 switch (reg) {
 case SPDIFTX_CR:
 case SPDIFTX_MR:
 case SPDIFTX_CDR:
 case SPDIFTX_IER:
 case SPDIFTX_IDR:
 case SPDIFTX_CH1UD(0):
 case SPDIFTX_CH1UD(1):
 case SPDIFTX_CH1UD(2):
 case SPDIFTX_CH1UD(3):
 case SPDIFTX_CH1UD(4):
 case SPDIFTX_CH1UD(5):
 case SPDIFTX_CH1S(0):
 case SPDIFTX_CH1S(1):
 case SPDIFTX_CH1S(2):
 case SPDIFTX_CH1S(3):
 case SPDIFTX_CH1S(4):
 case SPDIFTX_CH1S(5):
  return true;
 default:
  return false;
 }
}

static bool mchp_spdiftx_precious_reg(struct device *dev, unsigned int reg)
{
 switch (reg) {
 case SPDIFTX_CDR:
 case SPDIFTX_ISR:
  return true;
 default:
  return false;
 }
}

static const struct regmap_config mchp_spdiftx_regmap_config = {
 .reg_bits = 32,
 .reg_stride = 4,
 .val_bits = 32,
 .max_register = SPDIFTX_VERSION,
 .readable_reg = mchp_spdiftx_readable_reg,
 .writeable_reg = mchp_spdiftx_writeable_reg,
 .precious_reg = mchp_spdiftx_precious_reg,
 .cache_type = REGCACHE_FLAT,
};

#define SPDIFTX_GCLK_RATIO 128

#define SPDIFTX_CS_BITS  192
#define SPDIFTX_UD_BITS  192

struct mchp_spdiftx_mixer_control {
 unsigned char    ch_stat[SPDIFTX_CS_BITS / 8];
 unsigned char    user_data[SPDIFTX_UD_BITS / 8];
 spinlock_t    lock; /* exclusive access to control data */
};

struct mchp_spdiftx_dev {
 struct mchp_spdiftx_mixer_control control;
 struct snd_dmaengine_dai_dma_data playback;
 struct device    *dev;
 struct regmap    *regmap;
 struct clk    *pclk;
 struct clk    *gclk;
 unsigned int    fmt;
 unsigned int    suspend_irq;
};

static inline int mchp_spdiftx_is_running(struct mchp_spdiftx_dev *dev)
{
 u32 mr;

 regmap_read(dev->regmap, SPDIFTX_MR, &mr);
 return !!(mr & SPDIFTX_MR_TXEN_ENABLE);
}

static void mchp_spdiftx_channel_status_write(struct mchp_spdiftx_dev *dev)
{
 struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
 u32 val;
 int i;

 for (i = 0; i < ARRAY_SIZE(ctrl->ch_stat) / 4; i++) {
  val = (ctrl->ch_stat[(i * 4) + 0] << 0) |
        (ctrl->ch_stat[(i * 4) + 1] << 8) |
        (ctrl->ch_stat[(i * 4) + 2] << 16) |
        (ctrl->ch_stat[(i * 4) + 3] << 24);

  regmap_write(dev->regmap, SPDIFTX_CH1S(i), val);
 }
}

static void mchp_spdiftx_user_data_write(struct mchp_spdiftx_dev *dev)
{
 struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
 u32 val;
 int i;

 for (i = 0; i < ARRAY_SIZE(ctrl->user_data) / 4; i++) {
  val = (ctrl->user_data[(i * 4) + 0] << 0) |
        (ctrl->user_data[(i * 4) + 1] << 8) |
        (ctrl->user_data[(i * 4) + 2] << 16) |
        (ctrl->user_data[(i * 4) + 3] << 24);

  regmap_write(dev->regmap, SPDIFTX_CH1UD(i), val);
 }
}

static irqreturn_t mchp_spdiftx_interrupt(int irq, void *dev_id)
{
 struct mchp_spdiftx_dev *dev = dev_id;
 struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
 u32 sr, imr, pending, idr = 0;

 regmap_read(dev->regmap, SPDIFTX_ISR, &sr);
 regmap_read(dev->regmap, SPDIFTX_IMR, &imr);
 pending = sr & imr;

 if (!pending)
  return IRQ_NONE;

 if (pending & SPDIFTX_IR_TXUDR) {
  dev_warn(dev->dev, "underflow detected\n");
  idr |= SPDIFTX_IR_TXUDR;
 }

 if (pending & SPDIFTX_IR_TXOVR) {
  dev_warn(dev->dev, "overflow detected\n");
  idr |= SPDIFTX_IR_TXOVR;
 }

 if (pending & SPDIFTX_IR_UDRDY) {
  spin_lock(&ctrl->lock);
  mchp_spdiftx_user_data_write(dev);
  spin_unlock(&ctrl->lock);
  idr |= SPDIFTX_IR_UDRDY;
 }

 if (pending & SPDIFTX_IR_CSRDY) {
  spin_lock(&ctrl->lock);
  mchp_spdiftx_channel_status_write(dev);
  spin_unlock(&ctrl->lock);
  idr |= SPDIFTX_IR_CSRDY;
 }

 regmap_write(dev->regmap, SPDIFTX_IDR, idr);

 return IRQ_HANDLED;
}

static int mchp_spdiftx_dai_startup(struct snd_pcm_substream *substream,
        struct snd_soc_dai *dai)
{
 struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);

 /* Software reset the IP */
 regmap_write(dev->regmap, SPDIFTX_CR,
       SPDIFTX_CR_SWRST | SPDIFTX_CR_FCLR);

 return 0;
}

static void mchp_spdiftx_dai_shutdown(struct snd_pcm_substream *substream,
          struct snd_soc_dai *dai)
{
 struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);

 /* Disable interrupts */
 regmap_write(dev->regmap, SPDIFTX_IDR, 0xffffffff);
}

static int mchp_spdiftx_trigger(struct snd_pcm_substream *substream, int cmd,
    struct snd_soc_dai *dai)
{
 struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
 struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
 int ret;

 /* do not start/stop while channel status or user data is updated */
 spin_lock(&ctrl->lock);
 switch (cmd) {
 case SNDRV_PCM_TRIGGER_RESUME:
 case SNDRV_PCM_TRIGGER_START:
  regmap_write(dev->regmap, SPDIFTX_IER, dev->suspend_irq |
        SPDIFTX_IR_TXUDR | SPDIFTX_IR_TXOVR);
  dev->suspend_irq = 0;
  fallthrough;
 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
  ret = regmap_update_bits(dev->regmap, SPDIFTX_MR, SPDIFTX_MR_TXEN_MASK,
      SPDIFTX_MR_TXEN_ENABLE);
  break;
 case SNDRV_PCM_TRIGGER_SUSPEND:
  regmap_read(dev->regmap, SPDIFTX_IMR, &dev->suspend_irq);
  fallthrough;
 case SNDRV_PCM_TRIGGER_STOP:
  regmap_write(dev->regmap, SPDIFTX_IDR, dev->suspend_irq |
        SPDIFTX_IR_TXUDR | SPDIFTX_IR_TXOVR);
  fallthrough;
 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
  ret = regmap_update_bits(dev->regmap, SPDIFTX_MR, SPDIFTX_MR_TXEN_MASK,
      SPDIFTX_MR_TXEN_DISABLE);
  break;
 default:
  ret = -EINVAL;
 }
 spin_unlock(&ctrl->lock);
 if (ret)
  dev_err(dev->dev, "unable to start/stop TX: %d\n", ret);

 return ret;
}

static int mchp_spdiftx_hw_params(struct snd_pcm_substream *substream,
      struct snd_pcm_hw_params *params,
      struct snd_soc_dai *dai)
{
 unsigned long flags;
 struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
 struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
 u32 mr;
 unsigned int bps = params_physical_width(params) / 8;
 unsigned char aes3;
 int ret;

 dev_dbg(dev->dev, "%s() rate=%u format=%#x width=%u channels=%u\n",
  __func__, params_rate(params), params_format(params),
  params_width(params), params_channels(params));

 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
  dev_err(dev->dev, "Capture is not supported\n");
  return -EINVAL;
 }

 regmap_read(dev->regmap, SPDIFTX_MR, &mr);

 if (mr & SPDIFTX_MR_TXEN_ENABLE) {
  dev_err(dev->dev, "PCM already running\n");
  return -EBUSY;
 }

 /* Defaults: Toggle mode, justify to LSB, chunksize 1 */
 mr = SPDIFTX_MR_CMODE_TOGGLE_ACCESS | SPDIFTX_MR_JUSTIFY_LSB;
 dev->playback.maxburst = 1;
 switch (params_channels(params)) {
 case 1:
  mr |= SPDIFTX_MR_MULTICH_MONO;
  break;
 case 2:
  mr |= SPDIFTX_MR_MULTICH_DUAL;
  if (bps > 2)
   dev->playback.maxburst = 2;
  break;
 default:
  dev_err(dev->dev, "unsupported number of channels: %d\n",
   params_channels(params));
  return -EINVAL;
 }
 mr |= FIELD_PREP(SPDIFTX_MR_CHUNK_MASK, dev->playback.maxburst);

 switch (params_format(params)) {
 case SNDRV_PCM_FORMAT_S8:
  mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 8);
  break;
 case SNDRV_PCM_FORMAT_S16_BE:
  mr |= SPDIFTX_MR_ENDIAN_BIG;
  fallthrough;
 case SNDRV_PCM_FORMAT_S16_LE:
  mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 16);
  break;
 case SNDRV_PCM_FORMAT_S18_3BE:
  mr |= SPDIFTX_MR_ENDIAN_BIG;
  fallthrough;
 case SNDRV_PCM_FORMAT_S18_3LE:
  mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 18);
  break;
 case SNDRV_PCM_FORMAT_S20_3BE:
  mr |= SPDIFTX_MR_ENDIAN_BIG;
  fallthrough;
 case SNDRV_PCM_FORMAT_S20_3LE:
  mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 20);
  break;
 case SNDRV_PCM_FORMAT_S24_3BE:
  mr |= SPDIFTX_MR_ENDIAN_BIG;
  fallthrough;
 case SNDRV_PCM_FORMAT_S24_3LE:
  mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 24);
  break;
 case SNDRV_PCM_FORMAT_S24_BE:
  mr |= SPDIFTX_MR_ENDIAN_BIG;
  fallthrough;
 case SNDRV_PCM_FORMAT_S24_LE:
  mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 24);
  break;
 case SNDRV_PCM_FORMAT_S32_BE:
  mr |= SPDIFTX_MR_ENDIAN_BIG;
  fallthrough;
 case SNDRV_PCM_FORMAT_S32_LE:
  mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 32);
  break;
 default:
  dev_err(dev->dev, "unsupported PCM format: %d\n",
   params_format(params));
  return -EINVAL;
 }

 mr |= FIELD_PREP(SPDIFTX_MR_BPS_MASK, bps - 1);

 switch (params_rate(params)) {
 case 22050:
  aes3 = IEC958_AES3_CON_FS_22050;
  break;
 case 24000:
  aes3 = IEC958_AES3_CON_FS_24000;
  break;
 case 32000:
  aes3 = IEC958_AES3_CON_FS_32000;
  break;
 case 44100:
  aes3 = IEC958_AES3_CON_FS_44100;
  break;
 case 48000:
  aes3 = IEC958_AES3_CON_FS_48000;
  break;
 case 88200:
  aes3 = IEC958_AES3_CON_FS_88200;
  break;
 case 96000:
  aes3 = IEC958_AES3_CON_FS_96000;
  break;
 case 176400:
  aes3 = IEC958_AES3_CON_FS_176400;
  break;
 case 192000:
  aes3 = IEC958_AES3_CON_FS_192000;
  break;
 case 8000:
 case 11025:
 case 16000:
 case 64000:
  aes3 = IEC958_AES3_CON_FS_NOTID;
  break;
 default:
  dev_err(dev->dev, "unsupported sample frequency: %u\n",
   params_rate(params));
  return -EINVAL;
 }
 spin_lock_irqsave(&ctrl->lock, flags);
 ctrl->ch_stat[3] &= ~IEC958_AES3_CON_FS;
 ctrl->ch_stat[3] |= aes3;
 mchp_spdiftx_channel_status_write(dev);
 spin_unlock_irqrestore(&ctrl->lock, flags);

 /* GCLK is enabled by runtime PM. */
 clk_disable_unprepare(dev->gclk);

 ret = clk_set_rate(dev->gclk, params_rate(params) *
          SPDIFTX_GCLK_RATIO);
 if (ret) {
  dev_err(dev->dev,
   "unable to change gclk rate to: rate %u * ratio %u\n",
   params_rate(params), SPDIFTX_GCLK_RATIO);
  return ret;
 }
 ret = clk_prepare_enable(dev->gclk);
 if (ret) {
  dev_err(dev->dev, "unable to enable gclk: %d\n", ret);
  return ret;
 }

 dev_dbg(dev->dev, "%s(): GCLK set to %d\n", __func__,
  params_rate(params) * SPDIFTX_GCLK_RATIO);

 regmap_write(dev->regmap, SPDIFTX_MR, mr);

 return 0;
}

static int mchp_spdiftx_hw_free(struct snd_pcm_substream *substream,
    struct snd_soc_dai *dai)
{
 struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);

 return regmap_write(dev->regmap, SPDIFTX_CR,
       SPDIFTX_CR_SWRST | SPDIFTX_CR_FCLR);
}

#define MCHP_SPDIFTX_RATES SNDRV_PCM_RATE_8000_192000

#define MCHP_SPDIFTX_FORMATS (SNDRV_PCM_FMTBIT_S8 |  \
     SNDRV_PCM_FMTBIT_S16_LE | \
     SNDRV_PCM_FMTBIT_U16_BE | \
     SNDRV_PCM_FMTBIT_S18_3LE | \
     SNDRV_PCM_FMTBIT_S18_3BE | \
     SNDRV_PCM_FMTBIT_S20_3LE | \
     SNDRV_PCM_FMTBIT_S20_3BE | \
     SNDRV_PCM_FMTBIT_S24_3LE | \
     SNDRV_PCM_FMTBIT_S24_3BE | \
     SNDRV_PCM_FMTBIT_S24_LE | \
     SNDRV_PCM_FMTBIT_S24_BE | \
     SNDRV_PCM_FMTBIT_S32_LE | \
     SNDRV_PCM_FMTBIT_S32_BE \
     )

static int mchp_spdiftx_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 mchp_spdiftx_cs_get(struct snd_kcontrol *kcontrol,
          struct snd_ctl_elem_value *uvalue)
{
 unsigned long flags;
 struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
 struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
 struct mchp_spdiftx_mixer_control *ctrl = &dev->control;

 spin_lock_irqsave(&ctrl->lock, flags);
 memcpy(uvalue->value.iec958.status, ctrl->ch_stat,
        sizeof(ctrl->ch_stat));
 spin_unlock_irqrestore(&ctrl->lock, flags);

 return 0;
}

static int mchp_spdiftx_cs_put(struct snd_kcontrol *kcontrol,
          struct snd_ctl_elem_value *uvalue)
{
 unsigned long flags;
 struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
 struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
 struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
 int changed = 0;
 int i;

 spin_lock_irqsave(&ctrl->lock, flags);
 for (i = 0; i < ARRAY_SIZE(ctrl->ch_stat); i++) {
  if (ctrl->ch_stat[i] != uvalue->value.iec958.status[i])
   changed = 1;
  ctrl->ch_stat[i] = uvalue->value.iec958.status[i];
 }

 if (changed) {
  /* don't enable IP while we copy the channel status */
  if (mchp_spdiftx_is_running(dev)) {
   /*
 * if SPDIF is running, wait for interrupt to write
 * channel status
 */

   regmap_write(dev->regmap, SPDIFTX_IER,
         SPDIFTX_IR_CSRDY);
  } else {
   mchp_spdiftx_channel_status_write(dev);
  }
 }
 spin_unlock_irqrestore(&ctrl->lock, flags);

 return changed;
}

static int mchp_spdiftx_cs_mask(struct snd_kcontrol *kcontrol,
    struct snd_ctl_elem_value *uvalue)
{
 memset(uvalue->value.iec958.status, 0xff,
        sizeof(uvalue->value.iec958.status));

 return 0;
}

static int mchp_spdiftx_subcode_get(struct snd_kcontrol *kcontrol,
        struct snd_ctl_elem_value *uvalue)
{
 struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
 struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
 struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
 unsigned long flags;

 spin_lock_irqsave(&ctrl->lock, flags);
 memcpy(uvalue->value.iec958.subcode, ctrl->user_data,
        sizeof(ctrl->user_data));
 spin_unlock_irqrestore(&ctrl->lock, flags);

 return 0;
}

static int mchp_spdiftx_subcode_put(struct snd_kcontrol *kcontrol,
        struct snd_ctl_elem_value *uvalue)
{
 unsigned long flags;
 struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
 struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
 struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
 int changed = 0;
 int i;

 spin_lock_irqsave(&ctrl->lock, flags);
 for (i = 0; i < ARRAY_SIZE(ctrl->user_data); i++) {
  if (ctrl->user_data[i] != uvalue->value.iec958.subcode[i])
   changed = 1;

  ctrl->user_data[i] = uvalue->value.iec958.subcode[i];
 }
 if (changed) {
  if (mchp_spdiftx_is_running(dev)) {
   /*
 * if SPDIF is running, wait for interrupt to write
 * user data
 */

   regmap_write(dev->regmap, SPDIFTX_IER,
         SPDIFTX_IR_UDRDY);
  } else {
   mchp_spdiftx_user_data_write(dev);
  }
 }
 spin_unlock_irqrestore(&ctrl->lock, flags);

 return changed;
}

static struct snd_kcontrol_new mchp_spdiftx_ctrls[] = {
 /* Channel status controller */
 {
  .iface = SNDRV_CTL_ELEM_IFACE_PCM,
  .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
   SNDRV_CTL_ELEM_ACCESS_VOLATILE,
  .info = mchp_spdiftx_info,
  .get = mchp_spdiftx_cs_get,
  .put = mchp_spdiftx_cs_put,
 },
 {
  .iface = SNDRV_CTL_ELEM_IFACE_PCM,
  .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK),
  .access = SNDRV_CTL_ELEM_ACCESS_READ,
   SNDRV_CTL_ELEM_ACCESS_VOLATILE,
  .info = mchp_spdiftx_info,
  .get = mchp_spdiftx_cs_mask,
 },
 /* User bits controller */
 {
  .iface = SNDRV_CTL_ELEM_IFACE_PCM,
  .name = "IEC958 Subcode Playback Default",
  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
  .info = mchp_spdiftx_info,
  .get = mchp_spdiftx_subcode_get,
  .put = mchp_spdiftx_subcode_put,
 },
};

static int mchp_spdiftx_dai_probe(struct snd_soc_dai *dai)
{
 struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);

 snd_soc_dai_init_dma_data(dai, &dev->playback, NULL);

 /* Add controls */
 snd_soc_add_dai_controls(dai, mchp_spdiftx_ctrls,
     ARRAY_SIZE(mchp_spdiftx_ctrls));

 return 0;
}

static const struct snd_soc_dai_ops mchp_spdiftx_dai_ops = {
 .probe  = mchp_spdiftx_dai_probe,
 .startup = mchp_spdiftx_dai_startup,
 .shutdown = mchp_spdiftx_dai_shutdown,
 .trigger = mchp_spdiftx_trigger,
 .hw_params = mchp_spdiftx_hw_params,
 .hw_free = mchp_spdiftx_hw_free,
};

static struct snd_soc_dai_driver mchp_spdiftx_dai = {
 .name = "mchp-spdiftx",
 .playback = {
  .stream_name = "Playback",
  .channels_min = 1,
  .channels_max = 2,
  .rates = MCHP_SPDIFTX_RATES,
  .formats = MCHP_SPDIFTX_FORMATS,
 },
 .ops = &mchp_spdiftx_dai_ops,
};

static const struct snd_soc_component_driver mchp_spdiftx_component = {
 .name   = "mchp-spdiftx",
 .legacy_dai_naming = 1,
};

static const struct of_device_id mchp_spdiftx_dt_ids[] = {
 {
  .compatible = "microchip,sama7g5-spdiftx",
 },
 { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mchp_spdiftx_dt_ids);

static int mchp_spdiftx_runtime_suspend(struct device *dev)
{
 struct mchp_spdiftx_dev *spdiftx = dev_get_drvdata(dev);

 regcache_cache_only(spdiftx->regmap, true);

 clk_disable_unprepare(spdiftx->gclk);
 clk_disable_unprepare(spdiftx->pclk);

 return 0;
}

static int mchp_spdiftx_runtime_resume(struct device *dev)
{
 struct mchp_spdiftx_dev *spdiftx = dev_get_drvdata(dev);
 int ret;

 ret = clk_prepare_enable(spdiftx->pclk);
 if (ret) {
  dev_err(spdiftx->dev,
   "failed to enable the peripheral clock: %d\n", ret);
  return ret;
 }
 ret = clk_prepare_enable(spdiftx->gclk);
 if (ret) {
  dev_err(spdiftx->dev,
   "failed to enable generic clock: %d\n", ret);
  goto disable_pclk;
 }

 regcache_cache_only(spdiftx->regmap, false);
 regcache_mark_dirty(spdiftx->regmap);
 ret = regcache_sync(spdiftx->regmap);
 if (ret) {
  regcache_cache_only(spdiftx->regmap, true);
  clk_disable_unprepare(spdiftx->gclk);
disable_pclk:
  clk_disable_unprepare(spdiftx->pclk);
 }

 return ret;
}

static const struct dev_pm_ops mchp_spdiftx_pm_ops = {
 SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
 RUNTIME_PM_OPS(mchp_spdiftx_runtime_suspend, mchp_spdiftx_runtime_resume,
         NULL)
};

static int mchp_spdiftx_probe(struct platform_device *pdev)
{
 struct mchp_spdiftx_dev *dev;
 struct resource *mem;
 struct regmap *regmap;
 void __iomem *base;
 struct mchp_spdiftx_mixer_control *ctrl;
 int irq;
 int err;

 /* Get memory for driver data. */
 dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
 if (!dev)
  return -ENOMEM;

 /* Map I/O registers. */
 base = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);
 if (IS_ERR(base))
  return PTR_ERR(base);

 regmap = devm_regmap_init_mmio(&pdev->dev, base,
           &mchp_spdiftx_regmap_config);
 if (IS_ERR(regmap))
  return PTR_ERR(regmap);

 /* Request IRQ */
 irq = platform_get_irq(pdev, 0);
 if (irq < 0)
  return irq;

 err = devm_request_irq(&pdev->dev, irq, mchp_spdiftx_interrupt, 0,
          dev_name(&pdev->dev), dev);
 if (err)
  return err;

 /* Get the peripheral clock */
 dev->pclk = devm_clk_get(&pdev->dev, "pclk");
 if (IS_ERR(dev->pclk)) {
  err = PTR_ERR(dev->pclk);
  dev_err(&pdev->dev,
   "failed to get the peripheral clock: %d\n", err);
  return err;
 }

 /* Get the generic clock */
 dev->gclk = devm_clk_get(&pdev->dev, "gclk");
 if (IS_ERR(dev->gclk)) {
  err = PTR_ERR(dev->gclk);
  dev_err(&pdev->dev,
   "failed to get the PMC generic clock: %d\n", err);
  return err;
 }

 ctrl = &dev->control;
 spin_lock_init(&ctrl->lock);

 /* Init channel status */
 ctrl->ch_stat[0] = IEC958_AES0_CON_NOT_COPYRIGHT |
      IEC958_AES0_CON_EMPHASIS_NONE;

 dev->dev = &pdev->dev;
 dev->regmap = regmap;
 platform_set_drvdata(pdev, dev);

 pm_runtime_enable(dev->dev);
 if (!pm_runtime_enabled(dev->dev)) {
  err = mchp_spdiftx_runtime_resume(dev->dev);
  if (err)
   return err;
 }

 dev->playback.addr = (dma_addr_t)mem->start + SPDIFTX_CDR;
 dev->playback.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;

 err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
 if (err) {
  dev_err(&pdev->dev, "failed to register PMC: %d\n", err);
  goto pm_runtime_suspend;
 }

 err = devm_snd_soc_register_component(&pdev->dev,
           &mchp_spdiftx_component,
           &mchp_spdiftx_dai, 1);
 if (err) {
  dev_err(&pdev->dev, "failed to register component: %d\n", err);
  goto pm_runtime_suspend;
 }

 return 0;

pm_runtime_suspend:
 if (!pm_runtime_status_suspended(dev->dev))
  mchp_spdiftx_runtime_suspend(dev->dev);
 pm_runtime_disable(dev->dev);

 return err;
}

static void mchp_spdiftx_remove(struct platform_device *pdev)
{
 struct mchp_spdiftx_dev *dev = platform_get_drvdata(pdev);

 if (!pm_runtime_status_suspended(dev->dev))
  mchp_spdiftx_runtime_suspend(dev->dev);

 pm_runtime_disable(dev->dev);
}

static struct platform_driver mchp_spdiftx_driver = {
 .probe = mchp_spdiftx_probe,
 .remove = mchp_spdiftx_remove,
 .driver = {
  .name = "mchp_spdiftx",
  .of_match_table = mchp_spdiftx_dt_ids,
  .pm = pm_ptr(&mchp_spdiftx_pm_ops)
 },
};

module_platform_driver(mchp_spdiftx_driver);

MODULE_AUTHOR("Codrin Ciubotariu ");
MODULE_DESCRIPTION("Microchip S/PDIF TX Controller Driver");
MODULE_LICENSE("GPL v2");

Messung V0.5
C=95 H=97 G=95

¤ Dauer der Verarbeitung: 0.2 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.