Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/gpu/drm/nouveau/nvkm/subdev/pmu/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 1 kB image not shown  

SSL 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.2 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Versionsinformation zu Columbo

Bemerkung:

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Anfrage:

Dauer der Verarbeitung:

Sekunden

sprechenden Kalenders