Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  acp3x-pcm-dma.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0+
//
// AMD ALSA SoC PCM Driver
//
//Copyright 2016 Advanced Micro Devices, Inc.

#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/pm_runtime.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>

#include "acp3x.h"

#define DRV_NAME "acp3x_rv_i2s_dma"

static const struct snd_pcm_hardware acp3x_pcm_hardware_playback = {
 .info = SNDRV_PCM_INFO_INTERLEAVED |
  SNDRV_PCM_INFO_BLOCK_TRANSFER |
  SNDRV_PCM_INFO_BATCH |
  SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
  SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
 .formats = SNDRV_PCM_FMTBIT_S16_LE |  SNDRV_PCM_FMTBIT_S8 |
     SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
 .channels_min = 2,
 .channels_max = 8,
 .rates = SNDRV_PCM_RATE_8000_96000,
 .rate_min = 8000,
 .rate_max = 96000,
 .buffer_bytes_max = PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE,
 .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE,
 .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE,
 .periods_min = PLAYBACK_MIN_NUM_PERIODS,
 .periods_max = PLAYBACK_MAX_NUM_PERIODS,
};

static const struct snd_pcm_hardware acp3x_pcm_hardware_capture = {
 .info = SNDRV_PCM_INFO_INTERLEAVED |
  SNDRV_PCM_INFO_BLOCK_TRANSFER |
  SNDRV_PCM_INFO_BATCH |
  SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
  SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
 .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
     SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
 .channels_min = 2,
 .channels_max = 2,
 .rates = SNDRV_PCM_RATE_8000_48000,
 .rate_min = 8000,
 .rate_max = 48000,
 .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE,
 .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,
 .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE,
 .periods_min = CAPTURE_MIN_NUM_PERIODS,
 .periods_max = CAPTURE_MAX_NUM_PERIODS,
};

static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
{
 struct i2s_dev_data *rv_i2s_data;
 u16 play_flag, cap_flag;
 u32 val;

 rv_i2s_data = dev_id;
 if (!rv_i2s_data)
  return IRQ_NONE;

 play_flag = 0;
 cap_flag = 0;
 val = rv_readl(rv_i2s_data->acp3x_base + mmACP_EXTERNAL_INTR_STAT);
 if ((val & BIT(BT_TX_THRESHOLD)) && rv_i2s_data->play_stream) {
  rv_writel(BIT(BT_TX_THRESHOLD), rv_i2s_data->acp3x_base +
     mmACP_EXTERNAL_INTR_STAT);
  snd_pcm_period_elapsed(rv_i2s_data->play_stream);
  play_flag = 1;
 }
 if ((val & BIT(I2S_TX_THRESHOLD)) &&
    rv_i2s_data->i2ssp_play_stream) {
  rv_writel(BIT(I2S_TX_THRESHOLD),
   rv_i2s_data->acp3x_base + mmACP_EXTERNAL_INTR_STAT);
  snd_pcm_period_elapsed(rv_i2s_data->i2ssp_play_stream);
  play_flag = 1;
 }

 if ((val & BIT(BT_RX_THRESHOLD)) && rv_i2s_data->capture_stream) {
  rv_writel(BIT(BT_RX_THRESHOLD), rv_i2s_data->acp3x_base +
     mmACP_EXTERNAL_INTR_STAT);
  snd_pcm_period_elapsed(rv_i2s_data->capture_stream);
  cap_flag = 1;
 }
 if ((val & BIT(I2S_RX_THRESHOLD)) &&
    rv_i2s_data->i2ssp_capture_stream) {
  rv_writel(BIT(I2S_RX_THRESHOLD),
    rv_i2s_data->acp3x_base + mmACP_EXTERNAL_INTR_STAT);
  snd_pcm_period_elapsed(rv_i2s_data->i2ssp_capture_stream);
  cap_flag = 1;
 }

 if (play_flag | cap_flag)
  return IRQ_HANDLED;
 else
  return IRQ_NONE;
}

static void config_acp3x_dma(struct i2s_stream_instance *rtd, int direction)
{
 u16 page_idx;
 u32 low, high, val, acp_fifo_addr, reg_fifo_addr;
 u32 reg_dma_size, reg_fifo_size;
 dma_addr_t addr;

 addr = rtd->dma_addr;

 if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
  switch (rtd->i2s_instance) {
  case I2S_BT_INSTANCE:
   val = ACP_SRAM_BT_PB_PTE_OFFSET;
   break;
  case I2S_SP_INSTANCE:
  default:
   val = ACP_SRAM_SP_PB_PTE_OFFSET;
  }
 } else {
  switch (rtd->i2s_instance) {
  case I2S_BT_INSTANCE:
   val = ACP_SRAM_BT_CP_PTE_OFFSET;
   break;
  case I2S_SP_INSTANCE:
  default:
   val = ACP_SRAM_SP_CP_PTE_OFFSET;
  }
 }
 /* Group Enable */
 rv_writel(ACP_SRAM_PTE_OFFSET | BIT(31), rtd->acp3x_base +
    mmACPAXI2AXI_ATU_BASE_ADDR_GRP_1);
 rv_writel(PAGE_SIZE_4K_ENABLE, rtd->acp3x_base +
    mmACPAXI2AXI_ATU_PAGE_SIZE_GRP_1);

 for (page_idx = 0; page_idx < rtd->num_pages; page_idx++) {
  /* Load the low address of page int ACP SRAM through SRBM */
  low = lower_32_bits(addr);
  high = upper_32_bits(addr);

  rv_writel(low, rtd->acp3x_base + mmACP_SCRATCH_REG_0 + val);
  high |= BIT(31);
  rv_writel(high, rtd->acp3x_base + mmACP_SCRATCH_REG_0 + val
    + 4);
  /* Move to next physically contiguous page */
  val += 8;
  addr += PAGE_SIZE;
 }

 if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
  switch (rtd->i2s_instance) {
  case I2S_BT_INSTANCE:
   reg_dma_size = mmACP_BT_TX_DMA_SIZE;
   acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
      BT_PB_FIFO_ADDR_OFFSET;
   reg_fifo_addr = mmACP_BT_TX_FIFOADDR;
   reg_fifo_size = mmACP_BT_TX_FIFOSIZE;
   rv_writel(I2S_BT_TX_MEM_WINDOW_START,
    rtd->acp3x_base + mmACP_BT_TX_RINGBUFADDR);
   break;

  case I2S_SP_INSTANCE:
  default:
   reg_dma_size = mmACP_I2S_TX_DMA_SIZE;
   acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
      SP_PB_FIFO_ADDR_OFFSET;
   reg_fifo_addr = mmACP_I2S_TX_FIFOADDR;
   reg_fifo_size = mmACP_I2S_TX_FIFOSIZE;
   rv_writel(I2S_SP_TX_MEM_WINDOW_START,
    rtd->acp3x_base + mmACP_I2S_TX_RINGBUFADDR);
  }
 } else {
  switch (rtd->i2s_instance) {
  case I2S_BT_INSTANCE:
   reg_dma_size = mmACP_BT_RX_DMA_SIZE;
   acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
      BT_CAPT_FIFO_ADDR_OFFSET;
   reg_fifo_addr = mmACP_BT_RX_FIFOADDR;
   reg_fifo_size = mmACP_BT_RX_FIFOSIZE;
   rv_writel(I2S_BT_RX_MEM_WINDOW_START,
    rtd->acp3x_base + mmACP_BT_RX_RINGBUFADDR);
   break;

  case I2S_SP_INSTANCE:
  default:
   reg_dma_size = mmACP_I2S_RX_DMA_SIZE;
   acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
      SP_CAPT_FIFO_ADDR_OFFSET;
   reg_fifo_addr = mmACP_I2S_RX_FIFOADDR;
   reg_fifo_size = mmACP_I2S_RX_FIFOSIZE;
   rv_writel(I2S_SP_RX_MEM_WINDOW_START,
    rtd->acp3x_base + mmACP_I2S_RX_RINGBUFADDR);
  }
 }
 rv_writel(DMA_SIZE, rtd->acp3x_base + reg_dma_size);
 rv_writel(acp_fifo_addr, rtd->acp3x_base + reg_fifo_addr);
 rv_writel(FIFO_SIZE, rtd->acp3x_base + reg_fifo_size);
 rv_writel(BIT(I2S_RX_THRESHOLD) | BIT(BT_RX_THRESHOLD)
  | BIT(I2S_TX_THRESHOLD) | BIT(BT_TX_THRESHOLD),
  rtd->acp3x_base + mmACP_EXTERNAL_INTR_CNTL);
}

static int acp3x_dma_open(struct snd_soc_component *component,
     struct snd_pcm_substream *substream)
{
 struct snd_pcm_runtime *runtime;
 struct snd_soc_pcm_runtime *prtd;
 struct i2s_dev_data *adata;
 struct i2s_stream_instance *i2s_data;
 int ret;

 runtime = substream->runtime;
 prtd = snd_soc_substream_to_rtd(substream);
 component = snd_soc_rtdcom_lookup(prtd, DRV_NAME);
 adata = dev_get_drvdata(component->dev);
 i2s_data = kzalloc(sizeof(*i2s_data), GFP_KERNEL);
 if (!i2s_data)
  return -EINVAL;

 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
  runtime->hw = acp3x_pcm_hardware_playback;
 else
  runtime->hw = acp3x_pcm_hardware_capture;

 ret = snd_pcm_hw_constraint_integer(runtime,
         SNDRV_PCM_HW_PARAM_PERIODS);
 if (ret < 0) {
  dev_err(component->dev, "set integer constraint failed\n");
  kfree(i2s_data);
  return ret;
 }

 i2s_data->acp3x_base = adata->acp3x_base;
 runtime->private_data = i2s_data;
 return ret;
}


static int acp3x_dma_hw_params(struct snd_soc_component *component,
          struct snd_pcm_substream *substream,
          struct snd_pcm_hw_params *params)
{
 struct i2s_stream_instance *rtd;
 struct snd_soc_pcm_runtime *prtd;
 struct snd_soc_card *card;
 struct acp3x_platform_info *pinfo;
 struct i2s_dev_data *adata;
 u64 size;

 prtd = snd_soc_substream_to_rtd(substream);
 card = prtd->card;
 pinfo = snd_soc_card_get_drvdata(card);
 adata = dev_get_drvdata(component->dev);
 rtd = substream->runtime->private_data;
 if (!rtd)
  return -EINVAL;

 if (pinfo) {
  if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
   rtd->i2s_instance = pinfo->play_i2s_instance;
   switch (rtd->i2s_instance) {
   case I2S_BT_INSTANCE:
    adata->play_stream = substream;
    break;
   case I2S_SP_INSTANCE:
   default:
    adata->i2ssp_play_stream = substream;
   }
  } else {
   rtd->i2s_instance = pinfo->cap_i2s_instance;
   switch (rtd->i2s_instance) {
   case I2S_BT_INSTANCE:
    adata->capture_stream = substream;
    break;
   case I2S_SP_INSTANCE:
   default:
    adata->i2ssp_capture_stream = substream;
   }
  }
 } else {
  pr_err("pinfo failed\n");
 }
 size = params_buffer_bytes(params);
 rtd->dma_addr = substream->runtime->dma_addr;
 rtd->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT);
 config_acp3x_dma(rtd, substream->stream);
 return 0;
}

static snd_pcm_uframes_t acp3x_dma_pointer(struct snd_soc_component *component,
        struct snd_pcm_substream *substream)
{
 struct i2s_stream_instance *rtd;
 u32 pos;
 u32 buffersize;
 u64 bytescount;

 rtd = substream->runtime->private_data;

 buffersize = frames_to_bytes(substream->runtime,
         substream->runtime->buffer_size);
 bytescount = acp_get_byte_count(rtd, substream->stream);
 if (bytescount > rtd->bytescount)
  bytescount -= rtd->bytescount;
 pos = do_div(bytescount, buffersize);
 return bytes_to_frames(substream->runtime, pos);
}

static int acp3x_dma_new(struct snd_soc_component *component,
    struct snd_soc_pcm_runtime *rtd)
{
 struct device *parent = component->dev->parent;
 snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
           parent, MIN_BUFFER, MAX_BUFFER);
 return 0;
}

static int acp3x_dma_close(struct snd_soc_component *component,
      struct snd_pcm_substream *substream)
{
 struct snd_soc_pcm_runtime *prtd;
 struct i2s_dev_data *adata;
 struct i2s_stream_instance *ins;

 prtd = snd_soc_substream_to_rtd(substream);
 component = snd_soc_rtdcom_lookup(prtd, DRV_NAME);
 adata = dev_get_drvdata(component->dev);
 ins = substream->runtime->private_data;
 if (!ins)
  return -EINVAL;

 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
  switch (ins->i2s_instance) {
  case I2S_BT_INSTANCE:
   adata->play_stream = NULL;
   break;
  case I2S_SP_INSTANCE:
  default:
   adata->i2ssp_play_stream = NULL;
  }
 } else {
  switch (ins->i2s_instance) {
  case I2S_BT_INSTANCE:
   adata->capture_stream = NULL;
   break;
  case I2S_SP_INSTANCE:
  default:
   adata->i2ssp_capture_stream = NULL;
  }
 }

 return 0;
}

static const struct snd_soc_component_driver acp3x_i2s_component = {
 .name  = DRV_NAME,
 .open  = acp3x_dma_open,
 .close  = acp3x_dma_close,
 .hw_params = acp3x_dma_hw_params,
 .pointer = acp3x_dma_pointer,
 .pcm_construct = acp3x_dma_new,
};

static int acp3x_audio_probe(struct platform_device *pdev)
{
 struct resource *res;
 struct i2s_dev_data *adata;
 unsigned int irqflags;
 int status;

 if (!pdev->dev.platform_data) {
  dev_err(&pdev->dev, "platform_data not retrieved\n");
  return -ENODEV;
 }
 irqflags = *((unsigned int *)(pdev->dev.platform_data));

 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 if (!res) {
  dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
  return -ENODEV;
 }

 adata = devm_kzalloc(&pdev->dev, sizeof(*adata), GFP_KERNEL);
 if (!adata)
  return -ENOMEM;

 adata->acp3x_base = devm_ioremap(&pdev->dev, res->start,
      resource_size(res));
 if (!adata->acp3x_base)
  return -ENOMEM;

 status = platform_get_irq(pdev, 0);
 if (status < 0)
  return status;
 adata->i2s_irq = status;

 dev_set_drvdata(&pdev->dev, adata);
 status = devm_snd_soc_register_component(&pdev->dev,
       &acp3x_i2s_component,
       NULL, 0);
 if (status) {
  dev_err(&pdev->dev, "Fail to register acp i2s component\n");
  return -ENODEV;
 }
 status = devm_request_irq(&pdev->dev, adata->i2s_irq, i2s_irq_handler,
      irqflags, "ACP3x_I2S_IRQ", adata);
 if (status) {
  dev_err(&pdev->dev, "ACP3x I2S IRQ request failed\n");
  return -ENODEV;
 }

 pm_runtime_set_autosuspend_delay(&pdev->dev, 2000);
 pm_runtime_use_autosuspend(&pdev->dev);
 pm_runtime_mark_last_busy(&pdev->dev);
 pm_runtime_set_active(&pdev->dev);
 pm_runtime_enable(&pdev->dev);
 return 0;
}

static void acp3x_audio_remove(struct platform_device *pdev)
{
 pm_runtime_disable(&pdev->dev);
}

static int acp3x_resume(struct device *dev)
{
 struct i2s_dev_data *adata;
 u32 val, reg_val, frmt_val;

 reg_val = 0;
 frmt_val = 0;
 adata = dev_get_drvdata(dev);

 if (adata->play_stream && adata->play_stream->runtime) {
  struct i2s_stream_instance *rtd =
   adata->play_stream->runtime->private_data;
  config_acp3x_dma(rtd, SNDRV_PCM_STREAM_PLAYBACK);
  switch (rtd->i2s_instance) {
  case I2S_BT_INSTANCE:
   reg_val = mmACP_BTTDM_ITER;
   frmt_val = mmACP_BTTDM_TXFRMT;
   break;
  case I2S_SP_INSTANCE:
  default:
   reg_val = mmACP_I2STDM_ITER;
   frmt_val = mmACP_I2STDM_TXFRMT;
  }
  rv_writel((rtd->xfer_resolution  << 3),
     rtd->acp3x_base + reg_val);
 }
 if (adata->capture_stream && adata->capture_stream->runtime) {
  struct i2s_stream_instance *rtd =
   adata->capture_stream->runtime->private_data;
  config_acp3x_dma(rtd, SNDRV_PCM_STREAM_CAPTURE);
  switch (rtd->i2s_instance) {
  case I2S_BT_INSTANCE:
   reg_val = mmACP_BTTDM_IRER;
   frmt_val = mmACP_BTTDM_RXFRMT;
   break;
  case I2S_SP_INSTANCE:
  default:
   reg_val = mmACP_I2STDM_IRER;
   frmt_val = mmACP_I2STDM_RXFRMT;
  }
  rv_writel((rtd->xfer_resolution  << 3),
     rtd->acp3x_base + reg_val);
 }
 if (adata->tdm_mode == TDM_ENABLE) {
  rv_writel(adata->tdm_fmt, adata->acp3x_base + frmt_val);
  val = rv_readl(adata->acp3x_base + reg_val);
  rv_writel(val | 0x2, adata->acp3x_base + reg_val);
 }
 rv_writel(1, adata->acp3x_base + mmACP_EXTERNAL_INTR_ENB);
 return 0;
}


static int acp3x_pcm_runtime_suspend(struct device *dev)
{
 struct i2s_dev_data *adata;

 adata = dev_get_drvdata(dev);

 rv_writel(0, adata->acp3x_base + mmACP_EXTERNAL_INTR_ENB);

 return 0;
}

static int acp3x_pcm_runtime_resume(struct device *dev)
{
 struct i2s_dev_data *adata;

 adata = dev_get_drvdata(dev);

 rv_writel(1, adata->acp3x_base + mmACP_EXTERNAL_INTR_ENB);
 return 0;
}

static const struct dev_pm_ops acp3x_pm_ops = {
 .runtime_suspend = acp3x_pcm_runtime_suspend,
 .runtime_resume = acp3x_pcm_runtime_resume,
 .resume = acp3x_resume,
};

static struct platform_driver acp3x_dma_driver = {
 .probe = acp3x_audio_probe,
 .remove = acp3x_audio_remove,
 .driver = {
  .name = "acp3x_rv_i2s_dma",
  .pm = &acp3x_pm_ops,
 },
};

module_platform_driver(acp3x_dma_driver);

MODULE_AUTHOR("Vishnuvardhanrao.Ravulapati@amd.com");
MODULE_AUTHOR("Maruthi.Bayyavarapu@amd.com");
MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
MODULE_DESCRIPTION("AMD ACP 3.x PCM Driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:"DRV_NAME);

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

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






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge