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

Quelle  kmb_platform.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
//
// Copyright (C) 2020 Intel Corporation.
//
// Intel KeemBay Platform driver.
//

#include <linux/bitrev.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "kmb_platform.h"

#define PERIODS_MIN  2
#define PERIODS_MAX  48
#define PERIOD_BYTES_MIN 4096
#define BUFFER_BYTES_MAX (PERIODS_MAX * PERIOD_BYTES_MIN)
#define TDM_OPERATION  5
#define I2S_OPERATION  0
#define DATA_WIDTH_CONFIG_BIT 6
#define TDM_CHANNEL_CONFIG_BIT 3

static const struct snd_pcm_hardware kmb_pcm_hardware = {
 .info = SNDRV_PCM_INFO_INTERLEAVED |
  SNDRV_PCM_INFO_MMAP |
  SNDRV_PCM_INFO_MMAP_VALID |
  SNDRV_PCM_INFO_BATCH |
  SNDRV_PCM_INFO_BLOCK_TRANSFER,
 .rates = SNDRV_PCM_RATE_8000 |
   SNDRV_PCM_RATE_16000 |
   SNDRV_PCM_RATE_48000,
 .rate_min = 8000,
 .rate_max = 48000,
 .formats = SNDRV_PCM_FMTBIT_S16_LE |
     SNDRV_PCM_FMTBIT_S24_LE |
     SNDRV_PCM_FMTBIT_S32_LE |
     SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE,
 .channels_min = 2,
 .channels_max = 2,
 .buffer_bytes_max = BUFFER_BYTES_MAX,
 .period_bytes_min = PERIOD_BYTES_MIN,
 .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
 .periods_min = PERIODS_MIN,
 .periods_max = PERIODS_MAX,
 .fifo_size = 16,
};

/*
 * Convert to ADV7511 HDMI hardware format.
 * ADV7511 HDMI chip need parity bit replaced by block start bit and
 * with the preamble bits left out.
 * ALSA IEC958 subframe format:
 * bit 0-3  = preamble (0x8 = block start)
 *     4-7  = AUX (=0)
 *     8-27 = audio data (without AUX if 24bit sample)
 *     28   = validity
 *     29   = user data
 *     30   = channel status
 *     31   = parity
 *
 * ADV7511 IEC958 subframe format:
 * bit 0-23  = audio data
 *     24    = validity
 *     25    = user data
 *     26    = channel status
 *     27    = block start
 *     28-31 = 0
 * MSB to LSB bit reverse by software as hardware not supporting it.
 */

static void hdmi_reformat_iec958(struct snd_pcm_runtime *runtime,
     struct kmb_i2s_info *kmb_i2s,
     unsigned int tx_ptr)
{
 u32(*buf)[2] = (void *)runtime->dma_area;
 unsigned long temp;
 u32 i, j, sample;

 for (i = 0; i < kmb_i2s->fifo_th; i++) {
  j = 0;
  do {
   temp = buf[tx_ptr][j];
   /* Replace parity with block start*/
   assign_bit(31, &temp, (BIT(3) & temp));
   sample = bitrev32(temp);
   buf[tx_ptr][j] = sample << 4;
   j++;
  } while (j < 2);
  tx_ptr++;
 }
}

static unsigned int kmb_pcm_tx_fn(struct kmb_i2s_info *kmb_i2s,
      struct snd_pcm_runtime *runtime,
      unsigned int tx_ptr, bool *period_elapsed)
{
 unsigned int period_pos = tx_ptr % runtime->period_size;
 void __iomem *i2s_base = kmb_i2s->i2s_base;
 void *buf = runtime->dma_area;
 int i;

 if (kmb_i2s->iec958_fmt)
  hdmi_reformat_iec958(runtime, kmb_i2s, tx_ptr);

 /* KMB i2s uses two separate L/R FIFO */
 for (i = 0; i < kmb_i2s->fifo_th; i++) {
  if (kmb_i2s->config.data_width == 16) {
   writel(((u16(*)[2])buf)[tx_ptr][0], i2s_base + LRBR_LTHR(0));
   writel(((u16(*)[2])buf)[tx_ptr][1], i2s_base + RRBR_RTHR(0));
  } else {
   writel(((u32(*)[2])buf)[tx_ptr][0], i2s_base + LRBR_LTHR(0));
   writel(((u32(*)[2])buf)[tx_ptr][1], i2s_base + RRBR_RTHR(0));
  }

  period_pos++;

  if (++tx_ptr >= runtime->buffer_size)
   tx_ptr = 0;
 }

 *period_elapsed = period_pos >= runtime->period_size;

 return tx_ptr;
}

static unsigned int kmb_pcm_rx_fn(struct kmb_i2s_info *kmb_i2s,
      struct snd_pcm_runtime *runtime,
      unsigned int rx_ptr, bool *period_elapsed)
{
 unsigned int period_pos = rx_ptr % runtime->period_size;
 void __iomem *i2s_base = kmb_i2s->i2s_base;
 int chan = kmb_i2s->config.chan_nr;
 void *buf = runtime->dma_area;
 int i, j;

 /* KMB i2s uses two separate L/R FIFO */
 for (i = 0; i < kmb_i2s->fifo_th; i++) {
  for (j = 0; j < chan / 2; j++) {
   if (kmb_i2s->config.data_width == 16) {
    ((u16 *)buf)[rx_ptr * chan + (j * 2)] =
      readl(i2s_base + LRBR_LTHR(j));
    ((u16 *)buf)[rx_ptr * chan + ((j * 2) + 1)] =
      readl(i2s_base + RRBR_RTHR(j));
   } else {
    ((u32 *)buf)[rx_ptr * chan + (j * 2)] =
      readl(i2s_base + LRBR_LTHR(j));
    ((u32 *)buf)[rx_ptr * chan + ((j * 2) + 1)] =
      readl(i2s_base + RRBR_RTHR(j));
   }
  }
  period_pos++;

  if (++rx_ptr >= runtime->buffer_size)
   rx_ptr = 0;
 }

 *period_elapsed = period_pos >= runtime->period_size;

 return rx_ptr;
}

static inline void kmb_i2s_disable_channels(struct kmb_i2s_info *kmb_i2s,
         u32 stream)
{
 u32 i;

 /* Disable all channels regardless of configuration*/
 if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
  for (i = 0; i < MAX_ISR; i++)
   writel(0, kmb_i2s->i2s_base + TER(i));
 } else {
  for (i = 0; i < MAX_ISR; i++)
   writel(0, kmb_i2s->i2s_base + RER(i));
 }
}

static inline void kmb_i2s_clear_irqs(struct kmb_i2s_info *kmb_i2s, u32 stream)
{
 struct i2s_clk_config_data *config = &kmb_i2s->config;
 u32 i;

 if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
  for (i = 0; i < config->chan_nr / 2; i++)
   readl(kmb_i2s->i2s_base + TOR(i));
 } else {
  for (i = 0; i < config->chan_nr / 2; i++)
   readl(kmb_i2s->i2s_base + ROR(i));
 }
}

static inline void kmb_i2s_irq_trigger(struct kmb_i2s_info *kmb_i2s,
           u32 stream, int chan_nr, bool trigger)
{
 u32 i, irq;
 u32 flag;

 if (stream == SNDRV_PCM_STREAM_PLAYBACK)
  flag = TX_INT_FLAG;
 else
  flag = RX_INT_FLAG;

 for (i = 0; i < chan_nr / 2; i++) {
  irq = readl(kmb_i2s->i2s_base + IMR(i));

  if (trigger)
   irq = irq & ~flag;
  else
   irq = irq | flag;

  writel(irq, kmb_i2s->i2s_base + IMR(i));
 }
}

static void kmb_pcm_operation(struct kmb_i2s_info *kmb_i2s, bool playback)
{
 struct snd_pcm_substream *substream;
 bool period_elapsed;
 unsigned int new_ptr;
 unsigned int ptr;

 if (playback)
  substream = kmb_i2s->tx_substream;
 else
  substream = kmb_i2s->rx_substream;

 if (!substream || !snd_pcm_running(substream))
  return;

 if (playback) {
  ptr = kmb_i2s->tx_ptr;
  new_ptr = kmb_pcm_tx_fn(kmb_i2s, substream->runtime,
     ptr, &period_elapsed);
  cmpxchg(&kmb_i2s->tx_ptr, ptr, new_ptr);
 } else {
  ptr = kmb_i2s->rx_ptr;
  new_ptr = kmb_pcm_rx_fn(kmb_i2s, substream->runtime,
     ptr, &period_elapsed);
  cmpxchg(&kmb_i2s->rx_ptr, ptr, new_ptr);
 }

 if (period_elapsed)
  snd_pcm_period_elapsed(substream);
}

static int kmb_pcm_open(struct snd_soc_component *component,
   struct snd_pcm_substream *substream)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
 struct kmb_i2s_info *kmb_i2s;

 kmb_i2s = snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0));
 snd_soc_set_runtime_hwparams(substream, &kmb_pcm_hardware);
 snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
 runtime->private_data = kmb_i2s;

 return 0;
}

static int kmb_pcm_trigger(struct snd_soc_component *component,
      struct snd_pcm_substream *substream, int cmd)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 struct kmb_i2s_info *kmb_i2s = runtime->private_data;

 switch (cmd) {
 case SNDRV_PCM_TRIGGER_START:
  if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
   kmb_i2s->tx_ptr = 0;
   kmb_i2s->tx_substream = substream;
  } else {
   kmb_i2s->rx_ptr = 0;
   kmb_i2s->rx_substream = substream;
  }
  break;
 case SNDRV_PCM_TRIGGER_STOP:
  if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
   kmb_i2s->tx_substream = NULL;
  else
   kmb_i2s->rx_substream = NULL;
  kmb_i2s->iec958_fmt = false;
  break;
 default:
  return -EINVAL;
 }

 return 0;
}

static irqreturn_t kmb_i2s_irq_handler(int irq, void *dev_id)
{
 struct kmb_i2s_info *kmb_i2s = dev_id;
 struct i2s_clk_config_data *config = &kmb_i2s->config;
 irqreturn_t ret = IRQ_NONE;
 u32 tx_enabled = 0;
 u32 isr[4];
 int i;

 for (i = 0; i < config->chan_nr / 2; i++)
  isr[i] = readl(kmb_i2s->i2s_base + ISR(i));

 kmb_i2s_clear_irqs(kmb_i2s, SNDRV_PCM_STREAM_PLAYBACK);
 kmb_i2s_clear_irqs(kmb_i2s, SNDRV_PCM_STREAM_CAPTURE);
 /* Only check TX interrupt if TX is active */
 tx_enabled = readl(kmb_i2s->i2s_base + ITER);

 /*
 * Data available. Retrieve samples from FIFO
 */


 /*
 * 8 channel audio will have isr[0..2] triggered,
 * reading the specific isr based on the audio configuration,
 * to avoid reading the buffers too early.
 */

 switch (config->chan_nr) {
 case 2:
  if (isr[0] & ISR_RXDA)
   kmb_pcm_operation(kmb_i2s, false);
  ret = IRQ_HANDLED;
  break;
 case 4:
  if (isr[1] & ISR_RXDA)
   kmb_pcm_operation(kmb_i2s, false);
  ret = IRQ_HANDLED;
  break;
 case 8:
  if (isr[3] & ISR_RXDA)
   kmb_pcm_operation(kmb_i2s, false);
  ret = IRQ_HANDLED;
  break;
 }

 for (i = 0; i < config->chan_nr / 2; i++) {
  /*
 * Check if TX fifo is empty. If empty fill FIFO with samples
 */

  if ((isr[i] & ISR_TXFE) && tx_enabled) {
   kmb_pcm_operation(kmb_i2s, true);
   ret = IRQ_HANDLED;
  }

  /* Error Handling: TX */
  if (isr[i] & ISR_TXFO) {
   dev_dbg(kmb_i2s->dev, "TX overrun (ch_id=%d)\n", i);
   ret = IRQ_HANDLED;
  }
  /* Error Handling: RX */
  if (isr[i] & ISR_RXFO) {
   dev_dbg(kmb_i2s->dev, "RX overrun (ch_id=%d)\n", i);
   ret = IRQ_HANDLED;
  }
 }

 return ret;
}

static int kmb_platform_pcm_new(struct snd_soc_component *component,
    struct snd_soc_pcm_runtime *soc_runtime)
{
 size_t size = kmb_pcm_hardware.buffer_bytes_max;
 /* Use SNDRV_DMA_TYPE_CONTINUOUS as KMB doesn't use PCI sg buffer */
 snd_pcm_set_managed_buffer_all(soc_runtime->pcm,
           SNDRV_DMA_TYPE_CONTINUOUS,
           NULL, size, size);
 return 0;
}

static snd_pcm_uframes_t kmb_pcm_pointer(struct snd_soc_component *component,
      struct snd_pcm_substream *substream)
{
 struct snd_pcm_runtime *runtime = substream->runtime;
 struct kmb_i2s_info *kmb_i2s = runtime->private_data;
 snd_pcm_uframes_t pos;

 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
  pos = kmb_i2s->tx_ptr;
 else
  pos = kmb_i2s->rx_ptr;

 return pos < runtime->buffer_size ? pos : 0;
}

static const struct snd_soc_component_driver kmb_component = {
 .name   = "kmb",
 .pcm_construct  = kmb_platform_pcm_new,
 .open   = kmb_pcm_open,
 .trigger  = kmb_pcm_trigger,
 .pointer  = kmb_pcm_pointer,
 .legacy_dai_naming = 1,
};

static const struct snd_soc_component_driver kmb_component_dma = {
 .name   = "kmb",
 .legacy_dai_naming = 1,
};

static int kmb_probe(struct snd_soc_dai *cpu_dai)
{
 struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);

 if (kmb_i2s->use_pio)
  return 0;

 snd_soc_dai_init_dma_data(cpu_dai, &kmb_i2s->play_dma_data,
      &kmb_i2s->capture_dma_data);

 return 0;
}

static inline void kmb_i2s_enable_dma(struct kmb_i2s_info *kmb_i2s, u32 stream)
{
 u32 dma_reg;

 dma_reg = readl(kmb_i2s->i2s_base + I2S_DMACR);
 /* Enable DMA handshake for stream */
 if (stream == SNDRV_PCM_STREAM_PLAYBACK)
  dma_reg |= I2S_DMAEN_TXBLOCK;
 else
  dma_reg |= I2S_DMAEN_RXBLOCK;

 writel(dma_reg, kmb_i2s->i2s_base + I2S_DMACR);
}

static inline void kmb_i2s_disable_dma(struct kmb_i2s_info *kmb_i2s, u32 stream)
{
 u32 dma_reg;

 dma_reg = readl(kmb_i2s->i2s_base + I2S_DMACR);
 /* Disable DMA handshake for stream */
 if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
  dma_reg &= ~I2S_DMAEN_TXBLOCK;
  writel(1, kmb_i2s->i2s_base + I2S_RTXDMA);
 } else {
  dma_reg &= ~I2S_DMAEN_RXBLOCK;
  writel(1, kmb_i2s->i2s_base + I2S_RRXDMA);
 }
 writel(dma_reg, kmb_i2s->i2s_base + I2S_DMACR);
}

static void kmb_i2s_start(struct kmb_i2s_info *kmb_i2s,
     struct snd_pcm_substream *substream)
{
 struct i2s_clk_config_data *config = &kmb_i2s->config;

 /* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */
 writel(1, kmb_i2s->i2s_base + IER);

 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
  writel(1, kmb_i2s->i2s_base + ITER);
 else
  writel(1, kmb_i2s->i2s_base + IRER);

 if (kmb_i2s->use_pio)
  kmb_i2s_irq_trigger(kmb_i2s, substream->stream,
        config->chan_nr, true);
 else
  kmb_i2s_enable_dma(kmb_i2s, substream->stream);

 if (kmb_i2s->clock_provider)
  writel(1, kmb_i2s->i2s_base + CER);
 else
  writel(0, kmb_i2s->i2s_base + CER);
}

static void kmb_i2s_stop(struct kmb_i2s_info *kmb_i2s,
    struct snd_pcm_substream *substream)
{
 /* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */
 kmb_i2s_clear_irqs(kmb_i2s, substream->stream);

 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
  writel(0, kmb_i2s->i2s_base + ITER);
 else
  writel(0, kmb_i2s->i2s_base + IRER);

 kmb_i2s_irq_trigger(kmb_i2s, substream->stream, 8, false);

 if (!kmb_i2s->active) {
  writel(0, kmb_i2s->i2s_base + CER);
  writel(0, kmb_i2s->i2s_base + IER);
 }
}

static void kmb_disable_clk(void *clk)
{
 clk_disable_unprepare(clk);
}

static int kmb_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
{
 struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);
 int ret;

 switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
 case SND_SOC_DAIFMT_BC_FC:
  kmb_i2s->clock_provider = false;
  ret = 0;
  break;
 case SND_SOC_DAIFMT_BP_FP:
  writel(CLOCK_PROVIDER_MODE, kmb_i2s->pss_base + I2S_GEN_CFG_0);

  ret = clk_prepare_enable(kmb_i2s->clk_i2s);
  if (ret < 0)
   return ret;

  ret = devm_add_action_or_reset(kmb_i2s->dev, kmb_disable_clk,
            kmb_i2s->clk_i2s);
  if (ret)
   return ret;

  kmb_i2s->clock_provider = true;
  break;
 default:
  return -EINVAL;
 }

 return ret;
}

static int kmb_dai_trigger(struct snd_pcm_substream *substream,
      int cmd, struct snd_soc_dai *cpu_dai)
{
 struct kmb_i2s_info *kmb_i2s  = snd_soc_dai_get_drvdata(cpu_dai);

 switch (cmd) {
 case SNDRV_PCM_TRIGGER_START:
  /* Keep track of i2s activity before turn off
 * the i2s interface
 */

  kmb_i2s->active++;
  kmb_i2s_start(kmb_i2s, substream);
  break;
 case SNDRV_PCM_TRIGGER_STOP:
  kmb_i2s->active--;
  if (kmb_i2s->use_pio)
   kmb_i2s_stop(kmb_i2s, substream);
  break;
 default:
  return  -EINVAL;
 }

 return 0;
}

static void kmb_i2s_config(struct kmb_i2s_info *kmb_i2s, int stream)
{
 struct i2s_clk_config_data *config = &kmb_i2s->config;
 u32 ch_reg;

 kmb_i2s_disable_channels(kmb_i2s, stream);

 for (ch_reg = 0; ch_reg < config->chan_nr / 2; ch_reg++) {
  if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
   writel(kmb_i2s->xfer_resolution,
          kmb_i2s->i2s_base + TCR(ch_reg));

   writel(kmb_i2s->fifo_th - 1,
          kmb_i2s->i2s_base + TFCR(ch_reg));

   writel(1, kmb_i2s->i2s_base + TER(ch_reg));
  } else {
   writel(kmb_i2s->xfer_resolution,
          kmb_i2s->i2s_base + RCR(ch_reg));

   writel(kmb_i2s->fifo_th - 1,
          kmb_i2s->i2s_base + RFCR(ch_reg));

   writel(1, kmb_i2s->i2s_base + RER(ch_reg));
  }
 }
}

static int kmb_dai_hw_params(struct snd_pcm_substream *substream,
        struct snd_pcm_hw_params *hw_params,
        struct snd_soc_dai *cpu_dai)
{
 struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);
 struct i2s_clk_config_data *config = &kmb_i2s->config;
 u32 write_val;
 int ret;

 switch (params_format(hw_params)) {
 case SNDRV_PCM_FORMAT_S16_LE:
  config->data_width = 16;
  kmb_i2s->ccr = 0x00;
  kmb_i2s->xfer_resolution = 0x02;
  kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
  kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
  break;
 case SNDRV_PCM_FORMAT_S24_LE:
  config->data_width = 32;
  kmb_i2s->ccr = 0x14;
  kmb_i2s->xfer_resolution = 0x05;
  kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
  kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
  break;
 case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
  kmb_i2s->iec958_fmt = true;
  fallthrough;
 case SNDRV_PCM_FORMAT_S32_LE:
  config->data_width = 32;
  kmb_i2s->ccr = 0x10;
  kmb_i2s->xfer_resolution = 0x05;
  kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
  kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
  break;
 default:
  dev_err(kmb_i2s->dev, "kmb: unsupported PCM fmt");
  return -EINVAL;
 }

 config->chan_nr = params_channels(hw_params);

 switch (config->chan_nr) {
 case 8:
 case 4:
  /*
 * Platform is not capable of providing clocks for
 * multi channel audio
 */

  if (kmb_i2s->clock_provider)
   return -EINVAL;

  write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) |
    (config->data_width << DATA_WIDTH_CONFIG_BIT) |
    TDM_OPERATION;

  writel(write_val, kmb_i2s->pss_base + I2S_GEN_CFG_0);
  break;
 case 2:
  /*
 * Platform is only capable of providing clocks need for
 * 2 channel master mode
 */

  if (!(kmb_i2s->clock_provider))
   return -EINVAL;

  write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) |
    (config->data_width << DATA_WIDTH_CONFIG_BIT) |
    CLOCK_PROVIDER_MODE | I2S_OPERATION;

  writel(write_val, kmb_i2s->pss_base + I2S_GEN_CFG_0);
  break;
 default:
  dev_dbg(kmb_i2s->dev, "channel not supported\n");
  return -EINVAL;
 }

 kmb_i2s_config(kmb_i2s, substream->stream);

 writel(kmb_i2s->ccr, kmb_i2s->i2s_base + CCR);

 config->sample_rate = params_rate(hw_params);

 if (kmb_i2s->clock_provider) {
  /* Only 2 ch supported in Master mode */
  u32 bitclk = config->sample_rate * config->data_width * 2;

  ret = clk_set_rate(kmb_i2s->clk_i2s, bitclk);
  if (ret) {
   dev_err(kmb_i2s->dev,
    "Can't set I2S clock rate: %d\n", ret);
   return ret;
  }
 }

 return 0;
}

static int kmb_dai_prepare(struct snd_pcm_substream *substream,
      struct snd_soc_dai *cpu_dai)
{
 struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);

 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
  writel(1, kmb_i2s->i2s_base + TXFFR);
 else
  writel(1, kmb_i2s->i2s_base + RXFFR);

 return 0;
}

static int kmb_dai_startup(struct snd_pcm_substream *substream,
      struct snd_soc_dai *cpu_dai)
{
 struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);
 struct snd_dmaengine_dai_dma_data *dma_data;

 if (kmb_i2s->use_pio)
  return 0;

 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
  dma_data = &kmb_i2s->play_dma_data;
 else
  dma_data = &kmb_i2s->capture_dma_data;

 snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);

 return 0;
}

static int kmb_dai_hw_free(struct snd_pcm_substream *substream,
      struct snd_soc_dai *cpu_dai)
{
 struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);
 /* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */
 if (kmb_i2s->use_pio)
  kmb_i2s_clear_irqs(kmb_i2s, substream->stream);

 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
  writel(0, kmb_i2s->i2s_base + ITER);
 else
  writel(0, kmb_i2s->i2s_base + IRER);

 if (kmb_i2s->use_pio)
  kmb_i2s_irq_trigger(kmb_i2s, substream->stream, 8, false);
 else
  kmb_i2s_disable_dma(kmb_i2s, substream->stream);

 if (!kmb_i2s->active) {
  writel(0, kmb_i2s->i2s_base + CER);
  writel(0, kmb_i2s->i2s_base + IER);
 }

 return 0;
}

static const struct snd_soc_dai_ops kmb_dai_ops = {
 .probe  = kmb_probe,
 .startup = kmb_dai_startup,
 .trigger = kmb_dai_trigger,
 .hw_params = kmb_dai_hw_params,
 .hw_free = kmb_dai_hw_free,
 .prepare = kmb_dai_prepare,
 .set_fmt = kmb_set_dai_fmt,
};

static struct snd_soc_dai_driver intel_kmb_hdmi_dai[] = {
 {
  .name = "intel_kmb_hdmi_i2s",
  .playback = {
   .channels_min = 2,
   .channels_max = 2,
   .rates = SNDRV_PCM_RATE_48000,
   .rate_min = 48000,
   .rate_max = 48000,
   .formats = (SNDRV_PCM_FMTBIT_S16_LE |
        SNDRV_PCM_FMTBIT_S24_LE |
        SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE),
  },
  .ops = &kmb_dai_ops,
 },
};

static struct snd_soc_dai_driver intel_kmb_i2s_dai[] = {
 {
  .name = "intel_kmb_i2s",
  .playback = {
   .channels_min = 2,
   .channels_max = 2,
   .rates = SNDRV_PCM_RATE_8000 |
     SNDRV_PCM_RATE_16000 |
     SNDRV_PCM_RATE_48000,
   .rate_min = 8000,
   .rate_max = 48000,
   .formats = (SNDRV_PCM_FMTBIT_S32_LE |
        SNDRV_PCM_FMTBIT_S24_LE |
        SNDRV_PCM_FMTBIT_S16_LE),
  },
  .capture = {
   .channels_min = 2,
   .channels_max = 2,
   .rates = SNDRV_PCM_RATE_8000 |
     SNDRV_PCM_RATE_16000 |
     SNDRV_PCM_RATE_48000,
   .rate_min = 8000,
   .rate_max = 48000,
   .formats = (SNDRV_PCM_FMTBIT_S32_LE |
        SNDRV_PCM_FMTBIT_S24_LE |
        SNDRV_PCM_FMTBIT_S16_LE),
  },
  .ops = &kmb_dai_ops,
 },
};

static struct snd_soc_dai_driver intel_kmb_tdm_dai[] = {
 {
  .name = "intel_kmb_tdm",
  .capture = {
   .channels_min = 4,
   .channels_max = 8,
   .rates = SNDRV_PCM_RATE_8000 |
     SNDRV_PCM_RATE_16000 |
     SNDRV_PCM_RATE_48000,
   .rate_min = 8000,
   .rate_max = 48000,
   .formats = (SNDRV_PCM_FMTBIT_S32_LE |
        SNDRV_PCM_FMTBIT_S24_LE |
        SNDRV_PCM_FMTBIT_S16_LE),
  },
  .ops = &kmb_dai_ops,
 },
};

static const struct of_device_id kmb_plat_of_match[] = {
 { .compatible = "intel,keembay-i2s", .data = &intel_kmb_i2s_dai},
 { .compatible = "intel,keembay-hdmi-i2s", .data = &intel_kmb_hdmi_dai},
 { .compatible = "intel,keembay-tdm", .data = &intel_kmb_tdm_dai},
 {}
};
MODULE_DEVICE_TABLE(of, kmb_plat_of_match);

static int kmb_plat_dai_probe(struct platform_device *pdev)
{
 struct device_node *np = pdev->dev.of_node;
 struct snd_soc_dai_driver *kmb_i2s_dai;
 struct device *dev = &pdev->dev;
 struct kmb_i2s_info *kmb_i2s;
 struct resource *res;
 int ret, irq;
 u32 comp1_reg;

 kmb_i2s = devm_kzalloc(dev, sizeof(*kmb_i2s), GFP_KERNEL);
 if (!kmb_i2s)
  return -ENOMEM;

 kmb_i2s_dai = (struct snd_soc_dai_driver *)device_get_match_data(&pdev->dev);

 /* Prepare the related clocks */
 kmb_i2s->clk_apb = devm_clk_get(dev, "apb_clk");
 if (IS_ERR(kmb_i2s->clk_apb)) {
  dev_err(dev, "Failed to get apb clock\n");
  return PTR_ERR(kmb_i2s->clk_apb);
 }

 ret = clk_prepare_enable(kmb_i2s->clk_apb);
 if (ret < 0)
  return ret;

 ret = devm_add_action_or_reset(dev, kmb_disable_clk, kmb_i2s->clk_apb);
 if (ret) {
  dev_err(dev, "Failed to add clk_apb reset action\n");
  return ret;
 }

 kmb_i2s->clk_i2s = devm_clk_get(dev, "osc");
 if (IS_ERR(kmb_i2s->clk_i2s)) {
  dev_err(dev, "Failed to get osc clock\n");
  return PTR_ERR(kmb_i2s->clk_i2s);
 }

 kmb_i2s->i2s_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
 if (IS_ERR(kmb_i2s->i2s_base))
  return PTR_ERR(kmb_i2s->i2s_base);

 kmb_i2s->pss_base = devm_platform_ioremap_resource(pdev, 1);
 if (IS_ERR(kmb_i2s->pss_base))
  return PTR_ERR(kmb_i2s->pss_base);

 kmb_i2s->dev = &pdev->dev;

 comp1_reg = readl(kmb_i2s->i2s_base + I2S_COMP_PARAM_1);

 kmb_i2s->fifo_th = (1 << COMP1_FIFO_DEPTH(comp1_reg)) / 2;

 kmb_i2s->use_pio = !of_property_present(np, "dmas");

 if (kmb_i2s->use_pio) {
  irq = platform_get_irq_optional(pdev, 0);
  if (irq > 0) {
   ret = devm_request_irq(dev, irq, kmb_i2s_irq_handler, 0,
            pdev->name, kmb_i2s);
   if (ret < 0) {
    dev_err(dev, "failed to request irq\n");
    return ret;
   }
  }
  ret = devm_snd_soc_register_component(dev, &kmb_component,
            kmb_i2s_dai, 1);
 } else {
  kmb_i2s->play_dma_data.addr = res->start + I2S_TXDMA;
  kmb_i2s->capture_dma_data.addr = res->start + I2S_RXDMA;
  ret = snd_dmaengine_pcm_register(&pdev->dev,
       NULL, 0);
  if (ret) {
   dev_err(&pdev->dev, "could not register dmaengine: %d\n",
    ret);
   return ret;
  }
  ret = devm_snd_soc_register_component(dev, &kmb_component_dma,
            kmb_i2s_dai, 1);
 }

 if (ret) {
  dev_err(dev, "not able to register dai\n");
  return ret;
 }

 /* To ensure none of the channels are enabled at boot up */
 kmb_i2s_disable_channels(kmb_i2s, SNDRV_PCM_STREAM_PLAYBACK);
 kmb_i2s_disable_channels(kmb_i2s, SNDRV_PCM_STREAM_CAPTURE);

 dev_set_drvdata(dev, kmb_i2s);

 return ret;
}

static struct platform_driver kmb_plat_dai_driver = {
 .driver  = {
  .name  = "kmb-plat-dai",
  .of_match_table = kmb_plat_of_match,
 },
 .probe  = kmb_plat_dai_probe,
};

module_platform_driver(kmb_plat_dai_driver);

MODULE_DESCRIPTION("ASoC Intel KeemBay Platform driver");
MODULE_AUTHOR("Sia Jee Heng ");
MODULE_AUTHOR("Sit, Michael Wei Hong ");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:kmb_platform");

Messung V0.5
C=92 H=97 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.