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

Quelle  loongson1_ac97.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * AC97 Controller Driver for Loongson-1 SoC
 *
 * Copyright (C) 2025 Keguang Zhang <keguang.zhang@gmail.com>
 */


#include <linux/bitfield.h>
#include <linux/dma-mapping.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>

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

/* Loongson-1 AC97 Controller Registers */
#define AC97_CSR  0x0
#define AC97_OCC0  0x4
#define AC97_ICC  0x10
#define AC97_CRAC  0x18
#define AC97_INTRAW  0x54
#define AC97_INTM  0x58
#define AC97_INT_CW_CLR  0x68
#define AC97_INT_CR_CLR  0x6c

/* Control Status Register Bits (CSR) */
#define CSR_RESUME  BIT(1)
#define CSR_RST_FORCE  BIT(0)

/* MIC Channel Configuration Bits */
#define M_DMA_EN  BIT(22)
#define M_FIFO_THRES  GENMASK(21, 20)
#define M_FIFO_THRES_FULL FIELD_PREP(M_FIFO_THRES, 3)
#define M_FIFO_THRES_HALF FIELD_PREP(M_FIFO_THRES, 1)
#define M_FIFO_THRES_QUARTER FIELD_PREP(M_FIFO_THRES, 0)
#define M_SW   GENMASK(19, 18)
#define M_SW_16_BITS  FIELD_PREP(M_SW, 2)
#define M_SW_8_BITS  FIELD_PREP(M_SW, 0)
#define M_VSR   BIT(17)
#define M_CH_EN   BIT(16)
/* Right Channel Configuration Bits */
#define R_DMA_EN  BIT(14)
#define R_FIFO_THRES  GENMASK(13, 12)
#define R_FIFO_THRES_EMPTY FIELD_PREP(R_FIFO_THRES, 3)
#define R_FIFO_THRES_HALF FIELD_PREP(R_FIFO_THRES, 1)
#define R_FIFO_THRES_QUARTER FIELD_PREP(R_FIFO_THRES, 0)
#define R_SW   GENMASK(11, 10)
#define R_SW_16_BITS  FIELD_PREP(R_SW, 2)
#define R_SW_8_BITS  FIELD_PREP(R_SW, 0)
#define R_VSR   BIT(9)
#define R_CH_EN   BIT(8)
/* Left Channel Configuration Bits */
#define L_DMA_EN  BIT(6)
#define L_FIFO_THRES  GENMASK(5, 4)
#define L_FIFO_THRES_EMPTY FIELD_PREP(L_FIFO_THRES, 3)
#define L_FIFO_THRES_HALF FIELD_PREP(L_FIFO_THRES, 1)
#define L_FIFO_THRES_QUARTER FIELD_PREP(L_FIFO_THRES, 0)
#define L_SW   GENMASK(3, 2)
#define L_SW_16_BITS  FIELD_PREP(L_SW, 2)
#define L_SW_8_BITS  FIELD_PREP(L_SW, 0)
#define L_VSR   BIT(1)
#define L_CH_EN   BIT(0)

/* Codec Register Access Command Bits (CRAC) */
#define CODEC_WR  BIT(31)
#define CODEC_ADR  GENMASK(22, 16)
#define CODEC_DAT  GENMASK(15, 0)

/* Interrupt Register (INTRAW) */
#define CW_DONE   BIT(1)
#define CR_DONE   BIT(0)

#define LS1X_AC97_DMA_TX_EN  BIT(31)
#define LS1X_AC97_DMA_STEREO  BIT(30)
#define LS1X_AC97_DMA_TX_BYTES  GENMASK(29, 28)
#define LS1X_AC97_DMA_TX_4_BYTES FIELD_PREP(LS1X_AC97_DMA_TX_BYTES, 2)
#define LS1X_AC97_DMA_TX_2_BYTES FIELD_PREP(LS1X_AC97_DMA_TX_BYTES, 1)
#define LS1X_AC97_DMA_TX_1_BYTE  FIELD_PREP(LS1X_AC97_DMA_TX_BYTES, 0)
#define LS1X_AC97_DMA_DADDR_MASK GENMASK(27, 0)

#define LS1X_AC97_DMA_FIFO_SIZE  128

#define LS1X_AC97_TIMEOUT  3000

struct ls1x_ac97 {
 void __iomem *reg_base;
 struct regmap *regmap;
 dma_addr_t tx_dma_base;
 dma_addr_t rx_dma_base;
 struct snd_dmaengine_dai_dma_data capture_dma_data;
 struct snd_dmaengine_dai_dma_data playback_dma_data;
};

static struct ls1x_ac97 *ls1x_ac97;

static const struct regmap_config ls1x_ac97_regmap_config = {
 .reg_bits = 32,
 .val_bits = 32,
 .reg_stride = 4,
};

static void ls1x_ac97_reset(struct snd_ac97 *ac97)
{
 int val;

 regmap_write(ls1x_ac97->regmap, AC97_CSR, CSR_RST_FORCE);
 regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_CSR, val,
     !(val & CSR_RESUME), 0, LS1X_AC97_TIMEOUT);
}

static void ls1x_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val)
{
 int tmp, ret;

 tmp = FIELD_PREP(CODEC_ADR, reg) | FIELD_PREP(CODEC_DAT, val);
 regmap_write(ls1x_ac97->regmap, AC97_CRAC, tmp);
 ret = regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_INTRAW, tmp,
           (tmp & CW_DONE), 0, LS1X_AC97_TIMEOUT);
 if (ret)
  pr_err("timeout on AC97 write! %d\n", ret);

 regmap_read(ls1x_ac97->regmap, AC97_INT_CW_CLR, &ret);
}

static unsigned short ls1x_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
{
 int val, ret;

 val = CODEC_WR | FIELD_PREP(CODEC_ADR, reg);
 regmap_write(ls1x_ac97->regmap, AC97_CRAC, val);
 ret = regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_INTRAW, val,
           (val & CR_DONE), 0, LS1X_AC97_TIMEOUT);
 if (ret) {
  pr_err("timeout on AC97 read! %d\n", ret);
  return ret;
 }

 regmap_read(ls1x_ac97->regmap, AC97_INT_CR_CLR, &ret);
 regmap_read(ls1x_ac97->regmap, AC97_CRAC, &ret);

 return (ret & CODEC_DAT);
}

static void ls1x_ac97_init(struct snd_ac97 *ac97)
{
 writel(0, ls1x_ac97->reg_base + AC97_INTRAW);
 writel(0, ls1x_ac97->reg_base + AC97_INTM);

 /* Config output channels */
 regmap_update_bits(ls1x_ac97->regmap, AC97_OCC0,
      R_DMA_EN | R_FIFO_THRES | R_CH_EN |
      L_DMA_EN | L_FIFO_THRES | L_CH_EN,
      R_DMA_EN | R_FIFO_THRES_EMPTY | R_CH_EN |
      L_DMA_EN | L_FIFO_THRES_EMPTY | L_CH_EN);

 /* Config inputs channel */
 regmap_update_bits(ls1x_ac97->regmap, AC97_ICC,
      M_DMA_EN | M_FIFO_THRES | M_CH_EN |
      R_DMA_EN | R_FIFO_THRES | R_CH_EN |
      L_DMA_EN | L_FIFO_THRES | L_CH_EN,
      M_DMA_EN | M_FIFO_THRES_FULL | M_CH_EN |
      R_DMA_EN | R_FIFO_THRES_EMPTY | R_CH_EN |
      L_DMA_EN | L_FIFO_THRES_EMPTY | L_CH_EN);

 if (ac97->ext_id & AC97_EI_VRA) {
  regmap_update_bits(ls1x_ac97->regmap, AC97_OCC0, R_VSR | L_VSR, R_VSR | L_VSR);
  regmap_update_bits(ls1x_ac97->regmap, AC97_ICC, M_VSR, M_VSR);
 }
}

static struct snd_ac97_bus_ops ls1x_ac97_ops = {
 .reset = ls1x_ac97_reset,
 .write = ls1x_ac97_write,
 .read = ls1x_ac97_read,
 .init = ls1x_ac97_init,
};

static int ls1x_ac97_hw_params(struct snd_pcm_substream *substream,
          struct snd_pcm_hw_params *params,
          struct snd_soc_dai *cpu_dai)
{
 struct ls1x_ac97 *ac97 = dev_get_drvdata(cpu_dai->dev);
 struct snd_dmaengine_dai_dma_data *dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream);

 switch (params_channels(params)) {
 case 1:
  dma_data->addr &= ~LS1X_AC97_DMA_STEREO;
  break;
 case 2:
  dma_data->addr |= LS1X_AC97_DMA_STEREO;
  break;
 default:
  dev_err(cpu_dai->dev, "unsupported channels! %d\n", params_channels(params));
  return -EINVAL;
 }

 switch (params_format(params)) {
 case SNDRV_PCM_FORMAT_S8:
 case SNDRV_PCM_FORMAT_U8:
  if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
   regmap_update_bits(ac97->regmap, AC97_OCC0,
        R_SW | L_SW,
        R_SW_8_BITS | L_SW_8_BITS);
  else
   regmap_update_bits(ac97->regmap, AC97_ICC,
        M_SW | R_SW | L_SW,
        M_SW_8_BITS | R_SW_8_BITS | L_SW_8_BITS);
  break;
 case SNDRV_PCM_FORMAT_S16_LE:
 case SNDRV_PCM_FORMAT_U16_LE:
 case SNDRV_PCM_FORMAT_S16_BE:
 case SNDRV_PCM_FORMAT_U16_BE:
  if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
   regmap_update_bits(ac97->regmap, AC97_OCC0,
        R_SW | L_SW,
        R_SW_16_BITS | L_SW_16_BITS);
  else
   regmap_update_bits(ac97->regmap, AC97_ICC,
        M_SW | R_SW | L_SW,
        M_SW_16_BITS | R_SW_16_BITS | L_SW_16_BITS);
  break;
 default:
  dev_err(cpu_dai->dev, "unsupported format! %d\n", params_format(params));
  return -EINVAL;
 }

 return 0;
}

static int ls1x_ac97_dai_probe(struct snd_soc_dai *cpu_dai)
{
 struct ls1x_ac97 *ac97 = dev_get_drvdata(cpu_dai->dev);

 ac97->capture_dma_data.addr = ac97->rx_dma_base & LS1X_AC97_DMA_DADDR_MASK;
 ac97->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
 ac97->capture_dma_data.fifo_size = LS1X_AC97_DMA_FIFO_SIZE;

 ac97->playback_dma_data.addr = ac97->tx_dma_base & LS1X_AC97_DMA_DADDR_MASK;
 ac97->playback_dma_data.addr |= LS1X_AC97_DMA_TX_4_BYTES;
 ac97->playback_dma_data.addr |= LS1X_AC97_DMA_TX_EN;
 ac97->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
 ac97->playback_dma_data.fifo_size = LS1X_AC97_DMA_FIFO_SIZE;

 snd_soc_dai_init_dma_data(cpu_dai, &ac97->playback_dma_data, &ac97->capture_dma_data);
 snd_soc_dai_set_drvdata(cpu_dai, ac97);

 return 0;
}

static const struct snd_soc_dai_ops ls1x_ac97_dai_ops = {
 .probe  = ls1x_ac97_dai_probe,
 .hw_params = ls1x_ac97_hw_params,
};

#define LS1X_AC97_FMTS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |\
 SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |\
 SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE)

static struct snd_soc_dai_driver ls1x_ac97_dai[] = {
 {
  .name = "ls1x-ac97",
  .playback = {
   .stream_name = "AC97 Playback",
   .channels_min = 1,
   .channels_max = 2,
   .rates = SNDRV_PCM_RATE_8000_48000,
   .formats = LS1X_AC97_FMTS,
  },
  .capture = {
   .stream_name = "AC97 Capture",
   .channels_min = 1,
   .channels_max = 2,
   .rates = SNDRV_PCM_RATE_8000_48000,
   .formats = LS1X_AC97_FMTS,
  },
  .ops = &ls1x_ac97_dai_ops,
 },
};

static const struct snd_soc_component_driver ls1x_ac97_component = {
 .name = KBUILD_MODNAME,
 .legacy_dai_naming = 1,
};

static int ls1x_ac97_probe(struct platform_device *pdev)
{
 struct device *dev = &pdev->dev;
 struct ls1x_ac97 *ac97;
 struct resource *res;
 int ret;

 ac97 = devm_kzalloc(dev, sizeof(struct ls1x_ac97), GFP_KERNEL);
 if (!ac97)
  return -ENOMEM;
 ls1x_ac97 = ac97;
 platform_set_drvdata(pdev, ac97);

 ac97->reg_base = devm_platform_ioremap_resource(pdev, 0);
 if (IS_ERR(ac97->reg_base))
  return PTR_ERR(ac97->reg_base);

 ac97->regmap = devm_regmap_init_mmio(dev, ac97->reg_base, &ls1x_ac97_regmap_config);
 if (IS_ERR(ac97->regmap))
  return dev_err_probe(dev, PTR_ERR(ac97->regmap), "devm_regmap_init_mmio failed\n");

 res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "audio-tx");
 if (!res)
  return dev_err_probe(dev, -EINVAL, "Missing 'audio-tx' in reg-names property\n");

 ac97->tx_dma_base = dma_map_resource(dev, res->start, resource_size(res),
          DMA_TO_DEVICE, 0);
 if (dma_mapping_error(dev, ac97->tx_dma_base))
  return -ENXIO;

 res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "audio-rx");
 if (!res)
  return dev_err_probe(dev, -EINVAL, "Missing 'audio-rx' in reg-names property\n");

 ac97->rx_dma_base = dma_map_resource(dev, res->start, resource_size(res),
          DMA_FROM_DEVICE, 0);
 if (dma_mapping_error(dev, ac97->rx_dma_base))
  return -ENXIO;

 ret = devm_snd_dmaengine_pcm_register(dev, NULL, 0);
 if (ret)
  dev_err_probe(dev, ret, "failed to register PCM\n");

 ret = devm_snd_soc_register_component(dev, &ls1x_ac97_component,
           ls1x_ac97_dai, ARRAY_SIZE(ls1x_ac97_dai));
 if (ret)
  dev_err_probe(dev, ret, "failed to register DAI\n");

 return snd_soc_set_ac97_ops(&ls1x_ac97_ops);
}

static void ls1x_ac97_remove(struct platform_device *pdev)
{
 ls1x_ac97 = NULL;
 snd_soc_set_ac97_ops(NULL);
}

#ifdef CONFIG_PM_SLEEP
static int ls1x_ac97_suspend(struct device *dev)
{
 int val;

 regmap_clear_bits(ls1x_ac97->regmap, AC97_OCC0, R_DMA_EN | R_CH_EN | L_DMA_EN | L_CH_EN);
 regmap_clear_bits(ls1x_ac97->regmap, AC97_ICC,
     M_DMA_EN | M_CH_EN | R_DMA_EN | R_CH_EN | L_DMA_EN | L_CH_EN);
 regmap_set_bits(ls1x_ac97->regmap, AC97_CSR, CSR_RESUME);

 return regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_CSR, val,
     (val & CSR_RESUME), 0, LS1X_AC97_TIMEOUT);
}

static int ls1x_ac97_resume(struct device *dev)
{
 int val;

 regmap_set_bits(ls1x_ac97->regmap, AC97_OCC0, R_DMA_EN | R_CH_EN | L_DMA_EN | L_CH_EN);
 regmap_set_bits(ls1x_ac97->regmap, AC97_ICC,
   M_DMA_EN | M_CH_EN | R_DMA_EN | R_CH_EN | L_DMA_EN | L_CH_EN);
 regmap_set_bits(ls1x_ac97->regmap, AC97_CSR, CSR_RESUME);

 return regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_CSR, val,
     !(val & CSR_RESUME), 0, LS1X_AC97_TIMEOUT);
}
#endif

static const struct dev_pm_ops ls1x_ac97_pm_ops = {
 SET_SYSTEM_SLEEP_PM_OPS(ls1x_ac97_suspend, ls1x_ac97_resume)
};

static const struct of_device_id ls1x_ac97_match[] = {
 { .compatible = "loongson,ls1b-ac97" },
 { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, ls1x_ac97_match);

static struct platform_driver ls1x_ac97_driver = {
 .probe  = ls1x_ac97_probe,
 .remove  = ls1x_ac97_remove,
 .driver  = {
  .name = KBUILD_MODNAME,
  .of_match_table = ls1x_ac97_match,
  .pm = &ls1x_ac97_pm_ops,
 },
};

module_platform_driver(ls1x_ac97_driver);

MODULE_AUTHOR("Keguang Zhang ");
MODULE_DESCRIPTION("Loongson-1 AC97 Controller Driver");
MODULE_LICENSE("GPL");

Messung V0.5
C=97 H=100 G=98

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