diff mbox series

[1/3] ASoC: Add support for Loongson I2S controller

Message ID 20230605120934.2306548-1-mengyingkun@loongson.cn
State New
Headers show
Series [1/3] ASoC: Add support for Loongson I2S controller | expand

Commit Message

YingKun Meng June 5, 2023, 12:09 p.m. UTC
From: Yingkun Meng <mengyingkun@loongson.cn>

Loongson I2S controller is found on 7axxx/2kxxx chips from loongson,
it is a PCI device with two private DMA controllers, one for playback,
the other for capture.

The driver supports the use of DTS or ACPI to describe device resources.

Signed-off-by: Yingkun Meng <mengyingkun@loongson.cn>
---
 sound/soc/Kconfig                     |   1 +
 sound/soc/Makefile                    |   1 +
 sound/soc/loongson/Kconfig            |  15 +
 sound/soc/loongson/Makefile           |   4 +
 sound/soc/loongson/loongson_i2s.c     | 209 +++++++++++
 sound/soc/loongson/loongson_i2s.h     |  70 ++++
 sound/soc/loongson/loongson_i2s_pci.c | 478 ++++++++++++++++++++++++++
 7 files changed, 778 insertions(+)
 create mode 100644 sound/soc/loongson/Kconfig
 create mode 100644 sound/soc/loongson/Makefile
 create mode 100644 sound/soc/loongson/loongson_i2s.c
 create mode 100644 sound/soc/loongson/loongson_i2s.h
 create mode 100644 sound/soc/loongson/loongson_i2s_pci.c


base-commit: 1fbcc5ab1c7a172ef1159b154c296fe1e9ce209b

Comments

Mark Brown June 5, 2023, 1:17 p.m. UTC | #1
On Mon, Jun 05, 2023 at 08:09:32PM +0800, YingKun Meng wrote:

> +			regmap_read_poll_timeout_atomic(i2s->regmap,
> +						LS_I2S_CTRL, val,
> +						!(val & I2S_CTRL_MCLK_READY),
> +						10, 2000);

The driver is waiting for status bits to change in the regmap but...

> +		pr_err("buf len not multiply of period len\n");

Use dev_ functions to log things please.

> +static const struct regmap_config loongson_i2s_regmap_config = {
> +	.reg_bits = 32,
> +	.reg_stride = 4,
> +	.val_bits = 32,
> +	.max_register = 0x110,
> +	.cache_type = REGCACHE_FLAT,
> +};

...there are no volatile registers in the regmap so we will never read
from the hardware.  I don't understand how this can work?

> +	i2s->reg_base = pci_iomap(pdev, BAR_NUM, 0);
> +	if (!i2s->reg_base) {
> +		dev_err(&pdev->dev, "pci_iomap_error\n");
> +		ret = -EIO;
> +		goto err_release;
> +	}

pcim_iomap()?

> +	dev_set_name(&pdev->dev, "%s", loongson_i2s_dai.name);

Don't log static information like this, it just adds noise and makes the
boot slower.

> +	pci_disable_device(pdev);

pcim_enable_device() too.
Krzysztof Kozlowski June 5, 2023, 2:45 p.m. UTC | #2
On 05/06/2023 14:09, YingKun Meng wrote:
> From: Yingkun Meng <mengyingkun@loongson.cn>
> 
> The audio card uses loongson I2S controller present in 7axxx/2kxxx chips
> to transfer audio data.

Please use scripts/get_maintainers.pl to get a list of necessary people
and lists to CC.  It might happen, that command when run on an older
kernel, gives you outdated entries.  Therefore please be sure you base
your patches on recent Linux kernel.

You missed at least DT list (maybe more), so this won't be tested.
Please resend and include all necessary entries.

> 
> Signed-off-by: Yingkun Meng <mengyingkun@loongson.cn>
> ---
>  .../sound/loongson,ls-audio-card.yaml         | 64 +++++++++++++++++++
>  1 file changed, 64 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
> 
> diff --git a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
> new file mode 100644
> index 000000000000..f1d6ee346bb3
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
> @@ -0,0 +1,64 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/sound/loongson-audio-card.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Loongson generic ASoC audio sound card.

What is a "generic audio card"? Does it even match hardware? Bindings
are supposed to describe hardware, which is usually very specific.

Also: Drop full stop. It's a title.


> +
> +maintainers:
> +  - Yingkun Meng <mengyingkun@loongson.cn>
> +
> +description:
> +  Generic ASoC audio device for loongson platform.
> +
> +properties:
> +  compatible:
> +    const: loongson,ls-audio-card
> +
> +  model:
> +    $ref: /schemas/types.yaml#/definitions/string
> +    description: User specified audio sound card name
> +
> +  mclk-fs:
> +    $ref: simple-card.yaml#/definitions/mclk-fs
> +
> +  cpu:
> +    description: Holds subnode which indicates cpu dai.
> +    type: object
> +    additionalProperties: false
> +    properties:
> +      sound-dai:
> +        maxItems: 1

In the cpu: required with sound-dai

> +
> +  codec:
> +    description: Holds subnode which indicates codec dai.
> +    type: object
> +    additionalProperties: false
> +    properties:
> +      sound-dai:
> +        maxItems: 1

In the codec: required with sound-dai

No multiple dai links? Are you sure this card is so limited?


Best regards,
Krzysztof
Mark Brown June 5, 2023, 3:06 p.m. UTC | #3
On Mon, Jun 05, 2023 at 04:45:38PM +0200, Krzysztof Kozlowski wrote:
> On 05/06/2023 14:09, YingKun Meng wrote:

> > +title: Loongson generic ASoC audio sound card.

> What is a "generic audio card"? Does it even match hardware? Bindings
> are supposed to describe hardware, which is usually very specific.

The concept of a generic, reusable sound card seems reasonably clear -
there's a bunch of in tree examples already and the idea that we have to
pull together multiple bits of hardware to make a useful sound subsystem
is a known thing.

> Also: Drop full stop. It's a title.

Shouldn't this be checked by the tooling?
kernel test robot June 5, 2023, 6:35 p.m. UTC | #4
Hi YingKun,

kernel test robot noticed the following build errors:

[auto build test ERROR on 1fbcc5ab1c7a172ef1159b154c296fe1e9ce209b]

url:    https://github.com/intel-lab-lkp/linux/commits/YingKun-Meng/ASoC-loongson-Add-Loongson-Generic-ASoC-Sound-Card-Support/20230605-215940
base:   1fbcc5ab1c7a172ef1159b154c296fe1e9ce209b
patch link:    https://lore.kernel.org/r/20230605120934.2306548-1-mengyingkun%40loongson.cn
patch subject: [PATCH 1/3] ASoC: Add support for Loongson I2S controller
config: powerpc-allmodconfig (https://download.01.org/0day-ci/archive/20230606/202306060223.9hdivLrx-lkp@intel.com/config)
compiler: powerpc-linux-gcc (GCC) 12.3.0
reproduce (this is a W=1 build):
        mkdir -p ~/bin
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/cb79a1df70a849f772428740eb3c155da83de25b
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review YingKun-Meng/ASoC-loongson-Add-Loongson-Generic-ASoC-Sound-Card-Support/20230605-215940
        git checkout cb79a1df70a849f772428740eb3c155da83de25b
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.3.0 ~/bin/make.cross W=1 O=build_dir ARCH=powerpc olddefconfig
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.3.0 ~/bin/make.cross W=1 O=build_dir ARCH=powerpc SHELL=/bin/bash sound/soc/loongson/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202306060223.9hdivLrx-lkp@intel.com/

All error/warnings (new ones prefixed by >>):

   sound/soc/loongson/loongson_i2s_pci.c: In function 'dma_desc_save':
>> sound/soc/loongson/loongson_i2s_pci.c:82:17: error: implicit declaration of function 'readq'; did you mean 'readl'? [-Werror=implicit-function-declaration]
      82 |         val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK);
         |                 ^~~~~
         |                 readl
>> sound/soc/loongson/loongson_i2s_pci.c:84:9: error: implicit declaration of function 'writeq'; did you mean 'writel'? [-Werror=implicit-function-declaration]
      84 |         writeq(val, order_reg);
         |         ^~~~~~
         |         writel
   sound/soc/loongson/loongson_i2s_pci.c: In function 'loongson_pcm_hw_params':
>> sound/soc/loongson/loongson_i2s_pci.c:171:45: warning: right shift count >= width of type [-Wshift-count-overflow]
     171 |                 desc->order_hi = order_addr >> 32;
         |                                             ^~
   sound/soc/loongson/loongson_i2s_pci.c:174:43: warning: right shift count >= width of type [-Wshift-count-overflow]
     174 |                 desc->saddr_hi = mem_addr >> 32;
         |                                           ^~
   sound/soc/loongson/loongson_i2s_pci.c:190:57: warning: right shift count >= width of type [-Wshift-count-overflow]
     190 |                 desc->order_hi = prtd->dma_desc_arr_phy >> 32;
         |                                                         ^~
   sound/soc/loongson/loongson_i2s_pci.c: In function 'loongson_i2s_pci_probe':
>> sound/soc/loongson/loongson_i2s_pci.c:397:29: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
     397 |         tx_data->dev_addr = (u64)i2s->reg_base + LS_I2S_TX_DATA;
         |                             ^
   sound/soc/loongson/loongson_i2s_pci.c:400:29: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
     400 |         rx_data->dev_addr = (u64)i2s->reg_base + LS_I2S_RX_DATA;
         |                             ^
   cc1: some warnings being treated as errors


vim +82 sound/soc/loongson/loongson_i2s_pci.c

    74	
    75	static struct
    76	loongson_dma_desc *dma_desc_save(struct loongson_runtime_data *prtd)
    77	{
    78		void __iomem *order_reg = prtd->dma_data->order_addr;
    79		u64 val;
    80	
    81		val = (u64)prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK;
  > 82		val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK);
    83		val |= DMA_ORDER_ASK_VALID;
  > 84		writeq(val, order_reg);
    85	
    86		while (readl(order_reg) & DMA_ORDER_ASK_VALID)
    87			udelay(2);
    88	
    89		return prtd->dma_pos_desc;
    90	}
    91	
    92	static int loongson_pcm_trigger(struct snd_soc_component *component,
    93					struct snd_pcm_substream *substream, int cmd)
    94	{
    95		struct loongson_runtime_data *prtd = substream->runtime->private_data;
    96		struct device *dev = substream->pcm->card->dev;
    97		void __iomem *order_reg = prtd->dma_data->order_addr;
    98		u64 val;
    99		int ret = 0;
   100	
   101		switch (cmd) {
   102		case SNDRV_PCM_TRIGGER_START:
   103		case SNDRV_PCM_TRIGGER_RESUME:
   104		case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
   105			val = prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK;
   106			if (dev->coherent_dma_mask == DMA_BIT_MASK(64))
   107				val |= DMA_ORDER_ADDR_64;
   108			else
   109				val &= ~DMA_ORDER_ADDR_64;
   110			val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK);
   111			val |= DMA_ORDER_START;
   112			writeq(val, order_reg);
   113	
   114			while ((readl(order_reg) & DMA_ORDER_START))
   115				udelay(2);
   116			break;
   117		case SNDRV_PCM_TRIGGER_STOP:
   118		case SNDRV_PCM_TRIGGER_SUSPEND:
   119		case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
   120			dma_desc_save(prtd);
   121	
   122			/* dma stop */
   123			val = readq(order_reg) | DMA_ORDER_STOP;
   124			writeq(val, order_reg);
   125			udelay(1000);
   126	
   127			break;
   128		default:
   129			ret = -EINVAL;
   130		}
   131	
   132		return ret;
   133	}
   134	
   135	static int loongson_pcm_hw_params(struct snd_soc_component *component,
   136					  struct snd_pcm_substream *substream,
   137					  struct snd_pcm_hw_params *params)
   138	{
   139		struct snd_pcm_runtime *runtime = substream->runtime;
   140		struct loongson_runtime_data *prtd = runtime->private_data;
   141		size_t buf_len = params_buffer_bytes(params);
   142		size_t period_len = params_period_bytes(params);
   143		dma_addr_t order_addr, mem_addr;
   144		struct loongson_dma_desc *desc;
   145		u32 num_periods;
   146		int i;
   147	
   148		if (buf_len % period_len) {
   149			pr_err("buf len not multiply of period len\n");
   150			return -EINVAL;
   151		}
   152	
   153		num_periods = buf_len / period_len;
   154		if (!num_periods) {
   155			pr_err("dma data too small\n");
   156			return -EINVAL;
   157		}
   158	
   159		snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
   160		runtime->dma_bytes = buf_len;
   161	
   162		/* initialize dma descriptor array */
   163		mem_addr = runtime->dma_addr;
   164		order_addr = prtd->dma_desc_arr_phy;
   165		for (i = 0; i < num_periods; i++) {
   166			desc = &prtd->dma_desc_arr[i];
   167	
   168			/* next descriptor physical address */
   169			order_addr += sizeof(*desc);
   170			desc->order = order_addr | BIT(0);
 > 171			desc->order_hi = order_addr >> 32;
   172	
   173			desc->saddr = mem_addr;
   174			desc->saddr_hi = mem_addr >> 32;
   175			desc->daddr = prtd->dma_data->dev_addr;
   176	
   177			desc->cmd = BIT(0);
   178			if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
   179				desc->cmd |= BIT(12);
   180	
   181			desc->length = period_len >> 2;
   182			desc->step_length = 0;
   183			desc->step_times = 1;
   184	
   185			mem_addr += period_len;
   186		}
   187		if (num_periods > 0) {
   188			desc = &prtd->dma_desc_arr[num_periods - 1];
   189			desc->order = prtd->dma_desc_arr_phy | BIT(0);
   190			desc->order_hi = prtd->dma_desc_arr_phy >> 32;
   191		}
   192	
   193		/* init position descriptor */
   194		*prtd->dma_pos_desc = *prtd->dma_desc_arr;
   195	
   196		return 0;
   197	}
   198
kernel test robot June 5, 2023, 7:50 p.m. UTC | #5
Hi YingKun,

kernel test robot noticed the following build errors:

[auto build test ERROR on 1fbcc5ab1c7a172ef1159b154c296fe1e9ce209b]

url:    https://github.com/intel-lab-lkp/linux/commits/YingKun-Meng/ASoC-loongson-Add-Loongson-Generic-ASoC-Sound-Card-Support/20230605-215940
base:   1fbcc5ab1c7a172ef1159b154c296fe1e9ce209b
patch link:    https://lore.kernel.org/r/20230605120934.2306548-1-mengyingkun%40loongson.cn
patch subject: [PATCH 1/3] ASoC: Add support for Loongson I2S controller
config: sh-allmodconfig (https://download.01.org/0day-ci/archive/20230606/202306060320.Sphw0ihy-lkp@intel.com/config)
compiler: sh4-linux-gcc (GCC) 12.3.0
reproduce (this is a W=1 build):
        mkdir -p ~/bin
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/cb79a1df70a849f772428740eb3c155da83de25b
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review YingKun-Meng/ASoC-loongson-Add-Loongson-Generic-ASoC-Sound-Card-Support/20230605-215940
        git checkout cb79a1df70a849f772428740eb3c155da83de25b
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.3.0 ~/bin/make.cross W=1 O=build_dir ARCH=sh olddefconfig
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.3.0 ~/bin/make.cross W=1 O=build_dir ARCH=sh SHELL=/bin/bash sound/soc/loongson/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202306060320.Sphw0ihy-lkp@intel.com/

All error/warnings (new ones prefixed by >>):

   sound/soc/loongson/loongson_i2s_pci.c: In function 'loongson_pcm_hw_params':
   sound/soc/loongson/loongson_i2s_pci.c:171:45: warning: right shift count >= width of type [-Wshift-count-overflow]
     171 |                 desc->order_hi = order_addr >> 32;
         |                                             ^~
   sound/soc/loongson/loongson_i2s_pci.c:174:43: warning: right shift count >= width of type [-Wshift-count-overflow]
     174 |                 desc->saddr_hi = mem_addr >> 32;
         |                                           ^~
   sound/soc/loongson/loongson_i2s_pci.c:190:57: warning: right shift count >= width of type [-Wshift-count-overflow]
     190 |                 desc->order_hi = prtd->dma_desc_arr_phy >> 32;
         |                                                         ^~
   sound/soc/loongson/loongson_i2s_pci.c: In function 'loongson_i2s_pci_probe':
>> sound/soc/loongson/loongson_i2s_pci.c:364:15: error: implicit declaration of function 'pci_request_region'; did you mean 'pci_request_regions'? [-Werror=implicit-function-declaration]
     364 |         ret = pci_request_region(pdev, BAR_NUM, LS_I2S_DRVNAME);
         |               ^~~~~~~~~~~~~~~~~~
         |               pci_request_regions
>> sound/soc/loongson/loongson_i2s_pci.c:379:25: error: implicit declaration of function 'pci_iomap'; did you mean 'pcim_iomap'? [-Werror=implicit-function-declaration]
     379 |         i2s->reg_base = pci_iomap(pdev, BAR_NUM, 0);
         |                         ^~~~~~~~~
         |                         pcim_iomap
>> sound/soc/loongson/loongson_i2s_pci.c:379:23: warning: assignment to 'void *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
     379 |         i2s->reg_base = pci_iomap(pdev, BAR_NUM, 0);
         |                       ^
   sound/soc/loongson/loongson_i2s_pci.c:397:29: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
     397 |         tx_data->dev_addr = (u64)i2s->reg_base + LS_I2S_TX_DATA;
         |                             ^
   sound/soc/loongson/loongson_i2s_pci.c:400:29: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
     400 |         rx_data->dev_addr = (u64)i2s->reg_base + LS_I2S_RX_DATA;
         |                             ^
>> sound/soc/loongson/loongson_i2s_pci.c:441:9: error: implicit declaration of function 'pci_iounmap'; did you mean 'pcim_iounmap'? [-Werror=implicit-function-declaration]
     441 |         pci_iounmap(pdev, i2s->reg_base);
         |         ^~~~~~~~~~~
         |         pcim_iounmap
>> sound/soc/loongson/loongson_i2s_pci.c:443:9: error: implicit declaration of function 'pci_release_region'; did you mean 'pci_release_regions'? [-Werror=implicit-function-declaration]
     443 |         pci_release_region(pdev, BAR_NUM);
         |         ^~~~~~~~~~~~~~~~~~
         |         pci_release_regions
   sound/soc/loongson/loongson_i2s_pci.c: At top level:
>> sound/soc/loongson/loongson_i2s_pci.c:474:1: warning: data definition has no type or storage class
     474 | module_pci_driver(loongson_i2s_driver);
         | ^~~~~~~~~~~~~~~~~
>> sound/soc/loongson/loongson_i2s_pci.c:474:1: error: type defaults to 'int' in declaration of 'module_pci_driver' [-Werror=implicit-int]
>> sound/soc/loongson/loongson_i2s_pci.c:474:1: warning: parameter names (without types) in function declaration
   sound/soc/loongson/loongson_i2s_pci.c:464:26: warning: 'loongson_i2s_driver' defined but not used [-Wunused-variable]
     464 | static struct pci_driver loongson_i2s_driver = {
         |                          ^~~~~~~~~~~~~~~~~~~
   cc1: some warnings being treated as errors


vim +364 sound/soc/loongson/loongson_i2s_pci.c

   350	
   351	static int loongson_i2s_pci_probe(struct pci_dev *pdev,
   352					  const struct pci_device_id *pid)
   353	{
   354		const struct fwnode_handle *fwnode = pdev->dev.fwnode;
   355		struct loongson_dma_data *tx_data, *rx_data;
   356		struct loongson_i2s *i2s;
   357		int ret;
   358	
   359		if (pci_enable_device(pdev)) {
   360			dev_err(&pdev->dev, "pci_enable_device failed\n");
   361			return -ENODEV;
   362		}
   363	
 > 364		ret = pci_request_region(pdev, BAR_NUM, LS_I2S_DRVNAME);
   365		if (ret) {
   366			dev_err(&pdev->dev, "request regions failed %d\n", ret);
   367			goto err_disable;
   368		}
   369	
   370		i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
   371		if (!i2s) {
   372			ret = -ENOMEM;
   373			goto err_release;
   374		}
   375		i2s->rev_id = pdev->revision;
   376		i2s->dev = &pdev->dev;
   377		pci_set_drvdata(pdev, i2s);
   378	
 > 379		i2s->reg_base = pci_iomap(pdev, BAR_NUM, 0);
   380		if (!i2s->reg_base) {
   381			dev_err(&pdev->dev, "pci_iomap_error\n");
   382			ret = -EIO;
   383			goto err_release;
   384		}
   385	
   386		i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->reg_base,
   387						    &loongson_i2s_regmap_config);
   388		if (IS_ERR(i2s->regmap)) {
   389			dev_err(&pdev->dev, "Failed to initialize register map");
   390			ret = PTR_ERR(i2s->regmap);
   391			goto err_unmap;
   392		}
   393	
   394		tx_data = &i2s->tx_dma_data;
   395		rx_data = &i2s->rx_dma_data;
   396	
   397		tx_data->dev_addr = (u64)i2s->reg_base + LS_I2S_TX_DATA;
   398		tx_data->order_addr = i2s->reg_base + LS_I2S_TX_ORDER;
   399	
   400		rx_data->dev_addr = (u64)i2s->reg_base + LS_I2S_RX_DATA;
   401		rx_data->order_addr = i2s->reg_base + LS_I2S_RX_ORDER;
   402	
   403		tx_data->irq = fwnode_irq_get_byname(fwnode, "tx");
   404		if (tx_data->irq < 0) {
   405			dev_err(&pdev->dev, "dma tx irq invalid\n");
   406			ret = tx_data->irq;
   407			goto err_unmap;
   408		}
   409	
   410		rx_data->irq = fwnode_irq_get_byname(fwnode, "rx");
   411		if (rx_data->irq < 0) {
   412			dev_err(&pdev->dev, "dma rx irq invalid\n");
   413			ret = rx_data->irq;
   414			goto err_unmap;
   415		}
   416	
   417		device_property_read_u32(&pdev->dev, "clock-frequency", &i2s->clk_rate);
   418		if (!i2s->clk_rate) {
   419			dev_err(&pdev->dev, "clock-frequency property invalid\n");
   420			ret = -EINVAL;
   421			goto err_unmap;
   422		}
   423	
   424		dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
   425	
   426		dev_set_name(&pdev->dev, "%s", loongson_i2s_dai.name);
   427	
   428		loongson_i2s_init(i2s);
   429	
   430		ret = devm_snd_soc_register_component(&pdev->dev,
   431						      &loongson_i2s_component,
   432						      &loongson_i2s_dai, 1);
   433		if (ret) {
   434			dev_err(&pdev->dev, "register DAI failed %d\n", ret);
   435			goto err_unmap;
   436		}
   437	
   438		return 0;
   439	
   440	err_unmap:
 > 441		pci_iounmap(pdev, i2s->reg_base);
   442	err_release:
 > 443		pci_release_region(pdev, BAR_NUM);
   444	err_disable:
   445		pci_disable_device(pdev);
   446		return ret;
   447	}
   448	
   449	static void loongson_i2s_pci_remove(struct pci_dev *pdev)
   450	{
   451		struct loongson_i2s *i2s = dev_get_drvdata(&pdev->dev);
   452	
   453		pci_iounmap(pdev, i2s->reg_base);
   454		pci_release_region(pdev, BAR_NUM);
   455		pci_disable_device(pdev);
   456	}
   457	
   458	static const struct pci_device_id loongson_i2s_ids[] = {
   459		{ PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x7a27) },
   460		{ },
   461	};
   462	MODULE_DEVICE_TABLE(pci, loongson_i2s_ids);
   463	
   464	static struct pci_driver loongson_i2s_driver = {
   465		.name = "loongson-i2s-pci",
   466		.id_table = loongson_i2s_ids,
   467		.probe = loongson_i2s_pci_probe,
   468		.remove = loongson_i2s_pci_remove,
   469		.driver = {
   470			.owner = THIS_MODULE,
   471			.pm = pm_sleep_ptr(&loongson_i2s_pm),
   472		},
   473	};
 > 474	module_pci_driver(loongson_i2s_driver);
   475
YingKun Meng June 12, 2023, 1:44 a.m. UTC | #6
On 2023/6/5 21:17, Mark Brown wrote:
> On Mon, Jun 05, 2023 at 08:09:32PM +0800, YingKun Meng wrote:
>
>> +			regmap_read_poll_timeout_atomic(i2s->regmap,
>> +						LS_I2S_CTRL, val,
>> +						!(val & I2S_CTRL_MCLK_READY),
>> +						10, 2000);
> The driver is waiting for status bits to change in the regmap but...


Break condition reversed. Fixed in new version.

>
>> +		pr_err("buf len not multiply of period len\n");
> Use dev_ functions to log things please.


OK.

>> +static const struct regmap_config loongson_i2s_regmap_config = {
>> +	.reg_bits = 32,
>> +	.reg_stride = 4,
>> +	.val_bits = 32,
>> +	.max_register = 0x110,
>> +	.cache_type = REGCACHE_FLAT,
>> +};
> ...there are no volatile registers in the regmap so we will never read
> from the hardware.  I don't understand how this can work?
>

The I2S controller has two private DMA controllers to transfer the audio
data.
Its register set is divided into two parts: I2S control registers and
DMA control registers.

1) The I2S control registers are used to config I2S parameters, accessed
by regmap API. So we don't need to read back.

2) The DMA control registers are used to maintain the status of audio
data transmission.
These registers isn't maintained by regmap. They are accessed using
readx()/writex() APIs.

>> +	i2s->reg_base = pci_iomap(pdev, BAR_NUM, 0);
>> +	if (!i2s->reg_base) {
>> +		dev_err(&pdev->dev, "pci_iomap_error\n");
>> +		ret = -EIO;
>> +		goto err_release;
>> +	}
> pcim_iomap()?


OK.

>> +	dev_set_name(&pdev->dev, "%s", loongson_i2s_dai.name);
> Don't log static information like this, it just adds noise and makes the
> boot slower.


Removed in new version. Its original purpose is to set a fixed value for
platform component name, and match this value in machine driver.

>> +	pci_disable_device(pdev);
> pcim_enable_device() too.


OK.
YingKun Meng June 12, 2023, 2:30 a.m. UTC | #7
Hi Krzysztof,

Thanks for your kindly work.


On 2023/6/5 22:45, Krzysztof Kozlowski wrote:
> On 05/06/2023 14:09, YingKun Meng wrote:
>> From: Yingkun Meng <mengyingkun@loongson.cn>
>>
>> The audio card uses loongson I2S controller present in 7axxx/2kxxx chips
>> to transfer audio data.
> Please use scripts/get_maintainers.pl to get a list of necessary people
> and lists to CC.  It might happen, that command when run on an older
> kernel, gives you outdated entries.  Therefore please be sure you base
> your patches on recent Linux kernel.
>
> You missed at least DT list (maybe more), so this won't be tested.
> Please resend and include all necessary entries.
>

Sorry for my mistake. Fixed in new version.

>> Signed-off-by: Yingkun Meng <mengyingkun@loongson.cn>
>> ---
>>   .../sound/loongson,ls-audio-card.yaml         | 64 +++++++++++++++++++
>>   1 file changed, 64 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
>> new file mode 100644
>> index 000000000000..f1d6ee346bb3
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
>> @@ -0,0 +1,64 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/sound/loongson-audio-card.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Loongson generic ASoC audio sound card.
> What is a "generic audio card"? Does it even match hardware? Bindings
> are supposed to describe hardware, which is usually very specific.
>
> Also: Drop full stop. It's a title.
>
On loongson platform, the I2S controllers connect different codecs to
form different audio devices that can be driven by the same machine driver.
The "generic audio card" refers to these audio devices.

Currently, it can match some specific hardware, such as
2k2000 + es8323: the 2k2000 is a loogson SoC with a I2S controller
7a2000 + es8288: the 7a2000 is a bridge chip with a I2S controller


>> +
>> +maintainers:
>> +  - Yingkun Meng <mengyingkun@loongson.cn>
>> +
>> +description:
>> +  Generic ASoC audio device for loongson platform.
>> +
>> +properties:
>> +  compatible:
>> +    const: loongson,ls-audio-card
>> +
>> +  model:
>> +    $ref: /schemas/types.yaml#/definitions/string
>> +    description: User specified audio sound card name
>> +
>> +  mclk-fs:
>> +    $ref: simple-card.yaml#/definitions/mclk-fs
>> +
>> +  cpu:
>> +    description: Holds subnode which indicates cpu dai.
>> +    type: object
>> +    additionalProperties: false
>> +    properties:
>> +      sound-dai:
>> +        maxItems: 1
> In the cpu: required with sound-dai


OK.

>> +
>> +  codec:
>> +    description: Holds subnode which indicates codec dai.
>> +    type: object
>> +    additionalProperties: false
>> +    properties:
>> +      sound-dai:
>> +        maxItems: 1
> In the codec: required with sound-dai
>
> No multiple dai links? Are you sure this card is so limited?


Yes.  The audio device has only one channel.

>
> Best regards,
> Krzysztof


Thanks,

Yingkun Meng
YingKun Meng June 12, 2023, 2:38 a.m. UTC | #8
On 2023/6/5 23:06, Mark Brown wrote:
> On Mon, Jun 05, 2023 at 04:45:38PM +0200, Krzysztof Kozlowski wrote:
>> On 05/06/2023 14:09, YingKun Meng wrote:
>>> +title: Loongson generic ASoC audio sound card.
>> What is a "generic audio card"? Does it even match hardware? Bindings
>> are supposed to describe hardware, which is usually very specific.
> The concept of a generic, reusable sound card seems reasonably clear -
> there's a bunch of in tree examples already and the idea that we have to
> pull together multiple bits of hardware to make a useful sound subsystem
> is a known thing.
>
>> Also: Drop full stop. It's a title.
> Shouldn't this be checked by the tooling?


All work will be done in the new version.


Thanks,
Yingkun Meng
diff mbox series

Patch

diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 4b6e5a802880..bfa9622e1ab1 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -79,6 +79,7 @@  source "sound/soc/google/Kconfig"
 source "sound/soc/hisilicon/Kconfig"
 source "sound/soc/jz4740/Kconfig"
 source "sound/soc/kirkwood/Kconfig"
+source "sound/soc/loongson/Kconfig"
 source "sound/soc/img/Kconfig"
 source "sound/soc/intel/Kconfig"
 source "sound/soc/mediatek/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 9d9b228e4508..8376fdb217ed 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -46,6 +46,7 @@  obj-$(CONFIG_SND_SOC)	+= fsl/
 obj-$(CONFIG_SND_SOC)	+= google/
 obj-$(CONFIG_SND_SOC)	+= hisilicon/
 obj-$(CONFIG_SND_SOC)	+= jz4740/
+obj-$(CONFIG_SND_SOC)	+= loongson/
 obj-$(CONFIG_SND_SOC)	+= img/
 obj-$(CONFIG_SND_SOC)	+= intel/
 obj-$(CONFIG_SND_SOC)	+= mediatek/
diff --git a/sound/soc/loongson/Kconfig b/sound/soc/loongson/Kconfig
new file mode 100644
index 000000000000..fd477e7da6e0
--- /dev/null
+++ b/sound/soc/loongson/Kconfig
@@ -0,0 +1,15 @@ 
+# SPDX-License-Identifier: GPL-2.0
+menu "SoC Audio for Loongson CPUs"
+	depends on LOONGARCH || COMPILE_TEST
+
+config SND_SOC_LOONGSON_I2S_PCI
+	tristate "Loongson I2S-PCI Device Driver"
+	select REGMAP_MMIO
+	help
+	  Say Y or M if you want to add support for I2S driver for
+	  Loongson I2S controller.
+
+	  The controller is found in loongson bridge chips or SoCs,
+	  and work as a PCI device.
+
+endmenu
diff --git a/sound/soc/loongson/Makefile b/sound/soc/loongson/Makefile
new file mode 100644
index 000000000000..cfd0de1b1b22
--- /dev/null
+++ b/sound/soc/loongson/Makefile
@@ -0,0 +1,4 @@ 
+# SPDX-License-Identifier: GPL-2.0
+#Platform Support
+snd-soc-loongson-i2s-pci-objs := loongson_i2s_pci.o loongson_i2s.o
+obj-$(CONFIG_SND_SOC_LOONGSON_I2S_PCI) += snd-soc-loongson-i2s-pci.o
diff --git a/sound/soc/loongson/loongson_i2s.c b/sound/soc/loongson/loongson_i2s.c
new file mode 100644
index 000000000000..00793964237f
--- /dev/null
+++ b/sound/soc/loongson/loongson_i2s.c
@@ -0,0 +1,209 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Loongson-2K I2S master mode driver
+ *
+ * Copyright (C) 2022 Loongson Technology Corporation Limited
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/dma-mapping.h>
+#include <sound/soc.h>
+#include <linux/regmap.h>
+#include <sound/pcm_params.h>
+#include "loongson_i2s.h"
+
+#define LOONGSON_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
+			SNDRV_PCM_FMTBIT_S16_LE | \
+			SNDRV_PCM_FMTBIT_S20_3LE | \
+			SNDRV_PCM_FMTBIT_S24_LE)
+
+static int loongson_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+				struct snd_soc_dai *dai)
+{
+	struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+	u32 val;
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		/* Enable MCLK */
+		if (i2s->rev_id == 1) {
+			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
+					   I2S_CTRL_MCLK_EN,
+					   I2S_CTRL_MCLK_EN);
+			regmap_read_poll_timeout_atomic(i2s->regmap,
+						LS_I2S_CTRL, val,
+						!(val & I2S_CTRL_MCLK_READY),
+						10, 2000);
+		}
+
+		/* Enable master mode */
+		regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER,
+				   I2S_CTRL_MASTER);
+		if (i2s->rev_id == 1) {
+			regmap_read_poll_timeout_atomic(i2s->regmap,
+						LS_I2S_CTRL, val,
+						!(val & I2S_CTRL_CLK_READY),
+						10, 2000);
+		}
+		fallthrough;
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
+					   I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN,
+					   I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN);
+		else
+			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
+					   I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN,
+					   I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
+					I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN, 0);
+		else
+			regmap_update_bits(i2s->regmap, LS_I2S_CTRL,
+					I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN, 0);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int loongson_i2s_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *params,
+				  struct snd_soc_dai *dai)
+{
+	struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+	u32 clk_rate = i2s->clk_rate;
+	u32 sysclk = i2s->sysclk;
+	u32 bits = params_width(params);
+	u32 chans = params_channels(params);
+	u32 fs = params_rate(params);
+	u32 bclk_ratio, mclk_ratio;
+	u32 mclk_ratio_frac;
+	u32 val = 0;
+
+	if (i2s->rev_id == 0) {
+		bclk_ratio = DIV_ROUND_CLOSEST(clk_rate,
+					       (bits * chans * fs * 2)) - 1;
+		mclk_ratio = DIV_ROUND_CLOSEST(clk_rate, (sysclk * 2)) - 1;
+
+		/* According to 2k1000LA user manual, set bits == depth */
+		val |= (bits << 24);
+		val |= (bits << 16);
+		val |= (bclk_ratio << 8);
+		val |= mclk_ratio;
+		regmap_write(i2s->regmap, LS_I2S_CFG, val);
+	} else if (i2s->rev_id == 1) {
+		bclk_ratio = DIV_ROUND_CLOSEST(sysclk,
+					       (bits * chans * fs * 2)) - 1;
+		mclk_ratio = clk_rate / sysclk;
+		mclk_ratio_frac = DIV_ROUND_CLOSEST(((u64)clk_rate << 16),
+						    sysclk) - (mclk_ratio << 16);
+
+		regmap_read(i2s->regmap, LS_I2S_CFG, &val);
+		val |= (bits << 24);
+		val |= (bclk_ratio << 8);
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			val |= (bits << 16);
+		else
+			val |= bits;
+		regmap_write(i2s->regmap, LS_I2S_CFG, val);
+
+		val = (mclk_ratio_frac << 16) | mclk_ratio;
+		regmap_write(i2s->regmap, LS_I2S_CFG1, val);
+	} else
+		dev_err(i2s->dev, "I2S revision invalid\n");
+
+	return 0;
+}
+
+static int loongson_i2s_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+				       unsigned int freq, int dir)
+{
+	struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+	i2s->sysclk = freq;
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops loongson_i2s_dai_ops = {
+	.trigger	= loongson_i2s_trigger,
+	.hw_params	= loongson_i2s_hw_params,
+	.set_sysclk	= loongson_i2s_set_dai_sysclk,
+};
+
+static int loongson_i2s_dai_probe(struct snd_soc_dai *cpu_dai)
+{
+	struct loongson_i2s *i2s = dev_get_drvdata(cpu_dai->dev);
+
+	snd_soc_dai_init_dma_data(cpu_dai, &i2s->playback_dma_data,
+				  &i2s->capture_dma_data);
+	snd_soc_dai_set_drvdata(cpu_dai, i2s);
+
+	return 0;
+}
+
+struct snd_soc_dai_driver loongson_i2s_dai = {
+	.name = "loongson-i2s",
+	.probe = loongson_i2s_dai_probe,
+	.playback = {
+		.stream_name = "CPU-Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = LOONGSON_I2S_FORMATS,
+	},
+	.capture = {
+		.stream_name = "CPU-Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = LOONGSON_I2S_FORMATS,
+	},
+	.ops = &loongson_i2s_dai_ops,
+	.symmetric_rate = 1,
+};
+
+static int i2s_suspend(struct device *dev)
+{
+	struct loongson_i2s *i2s = dev_get_drvdata(dev);
+
+	regcache_cache_only(i2s->regmap, true);
+
+	return 0;
+}
+
+static int i2s_resume(struct device *dev)
+{
+	struct loongson_i2s *i2s = dev_get_drvdata(dev);
+	int ret;
+
+	regcache_cache_only(i2s->regmap, false);
+	regcache_mark_dirty(i2s->regmap);
+	ret = regcache_sync(i2s->regmap);
+
+	return ret;
+}
+
+const struct dev_pm_ops loongson_i2s_pm = {
+	SET_SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume)
+};
+
+void loongson_i2s_init(struct loongson_i2s *i2s)
+{
+	if (i2s->rev_id == 1) {
+		regmap_write(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET);
+		udelay(200);
+	}
+}
diff --git a/sound/soc/loongson/loongson_i2s.h b/sound/soc/loongson/loongson_i2s.h
new file mode 100644
index 000000000000..a743c34b8968
--- /dev/null
+++ b/sound/soc/loongson/loongson_i2s.h
@@ -0,0 +1,70 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ALSA I2S interface for the Loongson chip
+ *
+ */
+#ifndef _LOONGSON_I2S_H
+#define _LOONGSON_I2S_H
+
+#include <linux/regmap.h>
+#include <sound/dmaengine_pcm.h>
+
+/* I2S Common Registers */
+#define LS_I2S_VER	0x00 /* I2S Version */
+#define LS_I2S_CFG	0x04 /* I2S Config */
+#define LS_I2S_CTRL	0x08 /* I2S Control */
+#define LS_I2S_RX_DATA	0x0C /* I2S DMA RX Address */
+#define LS_I2S_TX_DATA	0x10 /* I2S DMA TX Address */
+
+/* 2K2000 I2S Specify Registers */
+#define LS_I2S_CFG1	0x14 /* I2S Config1 */
+
+/* 7A2000 I2S Specify Registers */
+#define LS_I2S_TX_ORDER	0x100 /* TX DMA Order */
+#define LS_I2S_RX_ORDER 0x110 /* RX DMA Order */
+
+/* Loongson I2S Control Register */
+#define I2S_CTRL_MCLK_READY	(1 << 16) /* MCLK ready */
+#define I2S_CTRL_MASTER		(1 << 15) /* Master mode */
+#define I2S_CTRL_MSB		(1 << 14) /* MSB bit order */
+#define I2S_CTRL_RX_EN		(1 << 13) /* RX enable */
+#define I2S_CTRL_TX_EN		(1 << 12) /* TX enable */
+#define I2S_CTRL_RX_DMA_EN	(1 << 11) /* DMA RX enable */
+#define I2S_CTRL_CLK_READY	(1 << 8)  /* BCLK ready */
+#define I2S_CTRL_TX_DMA_EN	(1 << 7)  /* DMA TX enable */
+#define I2S_CTRL_RESET		(1 << 4)  /* Controller soft reset */
+#define I2S_CTRL_MCLK_EN	(1 << 3)  /* Enable MCLK */
+#define I2S_CTRL_RX_INT_EN	(1 << 1)  /* RX interrupt enable */
+#define I2S_CTRL_TX_INT_EN	(1 << 0)  /* TX interrupt enable */
+
+#define LS_I2S_DRVNAME		"loongson-i2s"
+
+struct loongson_dma_data {
+	u64 dev_addr;			/* device physical address for DMA */
+	void __iomem *order_addr;	/* DMA order register */
+	u32 irq;			/* DMA irq */
+};
+
+struct loongson_i2s {
+	struct device *dev;
+	union {
+		struct snd_dmaengine_dai_dma_data playback_dma_data;
+		struct loongson_dma_data tx_dma_data;
+	};
+	union {
+		struct snd_dmaengine_dai_dma_data capture_dma_data;
+		struct loongson_dma_data rx_dma_data;
+	};
+	struct regmap *regmap;
+	void __iomem *reg_base;
+	u32 rev_id;
+	u32 clk_rate;
+	u32 sysclk;
+};
+
+extern const struct dev_pm_ops loongson_i2s_pm;
+extern struct snd_soc_dai_driver loongson_i2s_dai;
+
+void loongson_i2s_init(struct loongson_i2s *i2s);
+
+#endif
diff --git a/sound/soc/loongson/loongson_i2s_pci.c b/sound/soc/loongson/loongson_i2s_pci.c
new file mode 100644
index 000000000000..e4b8c654b12c
--- /dev/null
+++ b/sound/soc/loongson/loongson_i2s_pci.c
@@ -0,0 +1,478 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Loongson-2K I2S master mode driver
+ *
+ * Copyright (C) 2022 Loongson Technology Corporation Limited
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/dma-mapping.h>
+#include <linux/acpi.h>
+#include <linux/pci.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "loongson_i2s.h"
+
+/* DMA dma_order Register */
+#define DMA_ORDER_STOP          (1 << 4) /* DMA stop */
+#define DMA_ORDER_START         (1 << 3) /* DMA start */
+#define DMA_ORDER_ASK_VALID     (1 << 2) /* DMA ask valid flag */
+#define DMA_ORDER_AXI_UNCO      (1 << 1) /* Uncache access */
+#define DMA_ORDER_ADDR_64       (1 << 0) /* 64bits address support */
+
+#define DMA_ORDER_ASK_MASK      (~0x1fUL) /* Ask addr mask */
+#define DMA_ORDER_CTRL_MASK     (0x0fUL)  /* Control mask  */
+
+#define BAR_NUM 0
+
+/*
+ * DMA registers descriptor.
+ */
+struct loongson_dma_desc {
+	u32 order;		/* Next descriptor address register */
+	u32 saddr;		/* Source address register */
+	u32 daddr;		/* Device address register */
+	u32 length;		/* Total length register */
+	u32 step_length;	/* Memory stride register */
+	u32 step_times;		/* Repeat time register */
+	u32 cmd;		/* Command register */
+	u32 stats;		/* Status register */
+	u32 order_hi;		/* Next descriptor high address register */
+	u32 saddr_hi;		/* High source address register */
+	u32 res[6];		/* Reserved */
+};
+
+struct loongson_runtime_data {
+	struct loongson_dma_data *dma_data;
+
+	struct loongson_dma_desc *dma_desc_arr;
+	dma_addr_t dma_desc_arr_phy;
+
+	struct loongson_dma_desc *dma_pos_desc;
+	dma_addr_t dma_pos_desc_phy;
+};
+
+static const struct snd_pcm_hardware ls_pcm_hardware = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_RESUME |
+		SNDRV_PCM_INFO_PAUSE,
+	.formats = (SNDRV_PCM_FMTBIT_S8 |
+		SNDRV_PCM_FMTBIT_S16_LE |
+		SNDRV_PCM_FMTBIT_S20_3LE |
+		SNDRV_PCM_FMTBIT_S24_LE),
+	.period_bytes_min = 128,
+	.period_bytes_max = 128 * 1024,
+	.periods_min = 1,
+	.periods_max = PAGE_SIZE / sizeof(struct loongson_dma_desc),
+	.buffer_bytes_max = 1024 * 1024,
+};
+
+static struct
+loongson_dma_desc *dma_desc_save(struct loongson_runtime_data *prtd)
+{
+	void __iomem *order_reg = prtd->dma_data->order_addr;
+	u64 val;
+
+	val = (u64)prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK;
+	val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK);
+	val |= DMA_ORDER_ASK_VALID;
+	writeq(val, order_reg);
+
+	while (readl(order_reg) & DMA_ORDER_ASK_VALID)
+		udelay(2);
+
+	return prtd->dma_pos_desc;
+}
+
+static int loongson_pcm_trigger(struct snd_soc_component *component,
+				struct snd_pcm_substream *substream, int cmd)
+{
+	struct loongson_runtime_data *prtd = substream->runtime->private_data;
+	struct device *dev = substream->pcm->card->dev;
+	void __iomem *order_reg = prtd->dma_data->order_addr;
+	u64 val;
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		val = prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK;
+		if (dev->coherent_dma_mask == DMA_BIT_MASK(64))
+			val |= DMA_ORDER_ADDR_64;
+		else
+			val &= ~DMA_ORDER_ADDR_64;
+		val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK);
+		val |= DMA_ORDER_START;
+		writeq(val, order_reg);
+
+		while ((readl(order_reg) & DMA_ORDER_START))
+			udelay(2);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		dma_desc_save(prtd);
+
+		/* dma stop */
+		val = readq(order_reg) | DMA_ORDER_STOP;
+		writeq(val, order_reg);
+		udelay(1000);
+
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int loongson_pcm_hw_params(struct snd_soc_component *component,
+				  struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *params)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct loongson_runtime_data *prtd = runtime->private_data;
+	size_t buf_len = params_buffer_bytes(params);
+	size_t period_len = params_period_bytes(params);
+	dma_addr_t order_addr, mem_addr;
+	struct loongson_dma_desc *desc;
+	u32 num_periods;
+	int i;
+
+	if (buf_len % period_len) {
+		pr_err("buf len not multiply of period len\n");
+		return -EINVAL;
+	}
+
+	num_periods = buf_len / period_len;
+	if (!num_periods) {
+		pr_err("dma data too small\n");
+		return -EINVAL;
+	}
+
+	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+	runtime->dma_bytes = buf_len;
+
+	/* initialize dma descriptor array */
+	mem_addr = runtime->dma_addr;
+	order_addr = prtd->dma_desc_arr_phy;
+	for (i = 0; i < num_periods; i++) {
+		desc = &prtd->dma_desc_arr[i];
+
+		/* next descriptor physical address */
+		order_addr += sizeof(*desc);
+		desc->order = order_addr | BIT(0);
+		desc->order_hi = order_addr >> 32;
+
+		desc->saddr = mem_addr;
+		desc->saddr_hi = mem_addr >> 32;
+		desc->daddr = prtd->dma_data->dev_addr;
+
+		desc->cmd = BIT(0);
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			desc->cmd |= BIT(12);
+
+		desc->length = period_len >> 2;
+		desc->step_length = 0;
+		desc->step_times = 1;
+
+		mem_addr += period_len;
+	}
+	if (num_periods > 0) {
+		desc = &prtd->dma_desc_arr[num_periods - 1];
+		desc->order = prtd->dma_desc_arr_phy | BIT(0);
+		desc->order_hi = prtd->dma_desc_arr_phy >> 32;
+	}
+
+	/* init position descriptor */
+	*prtd->dma_pos_desc = *prtd->dma_desc_arr;
+
+	return 0;
+}
+
+static snd_pcm_uframes_t
+loongson_pcm_pointer(struct snd_soc_component *component,
+		     struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct loongson_runtime_data *prtd = runtime->private_data;
+	struct loongson_dma_desc *desc;
+	snd_pcm_uframes_t x;
+	u64 addr;
+
+	desc = dma_desc_save(prtd);
+	addr = ((u64)desc->saddr_hi << 32) | desc->saddr;
+
+	x = bytes_to_frames(runtime, addr - runtime->dma_addr);
+	if (x == runtime->buffer_size)
+		x = 0;
+	return x;
+}
+
+static irqreturn_t loongson_pcm_dma_irq(int irq, void *devid)
+{
+	struct snd_pcm_substream *substream = devid;
+
+	snd_pcm_period_elapsed(substream);
+	return IRQ_HANDLED;
+}
+
+static int loongson_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 = substream->private_data;
+	struct snd_card *card = substream->pcm->card;
+	struct loongson_runtime_data *prtd;
+	struct loongson_dma_data *dma_data;
+	int ret;
+
+	/*
+	 * For mysterious reasons (and despite what the manual says)
+	 * playback samples are lost if the DMA count is not a multiple
+	 * of the DMA burst size.  Let's add a rule to enforce that.
+	 */
+	snd_pcm_hw_constraint_step(runtime, 0,
+				   SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128);
+	snd_pcm_hw_constraint_step(runtime, 0,
+				   SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 128);
+	snd_pcm_hw_constraint_integer(substream->runtime,
+				      SNDRV_PCM_HW_PARAM_PERIODS);
+	snd_soc_set_runtime_hwparams(substream, &ls_pcm_hardware);
+
+	prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
+	if (!prtd)
+		return -ENOMEM;
+
+	prtd->dma_desc_arr = dma_alloc_coherent(card->dev, PAGE_SIZE,
+						&prtd->dma_desc_arr_phy,
+						GFP_KERNEL);
+	if (!prtd->dma_desc_arr) {
+		ret = -ENOMEM;
+		goto desc_err;
+	}
+
+	prtd->dma_pos_desc = dma_alloc_coherent(card->dev,
+						sizeof(*prtd->dma_pos_desc),
+						&prtd->dma_pos_desc_phy,
+						GFP_KERNEL);
+	if (!prtd->dma_pos_desc) {
+		ret = -ENOMEM;
+		goto pos_err;
+	}
+
+	dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
+	ret = request_irq(dma_data->irq, loongson_pcm_dma_irq,
+			  IRQF_TRIGGER_RISING, LS_I2S_DRVNAME, substream);
+	if (ret < 0)
+		goto irq_err;
+
+	prtd->dma_data = dma_data;
+	substream->runtime->private_data = prtd;
+
+	return 0;
+irq_err:
+	dma_free_coherent(card->dev, sizeof(*prtd->dma_pos_desc),
+			  prtd->dma_pos_desc, prtd->dma_pos_desc_phy);
+pos_err:
+	dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr,
+			  prtd->dma_desc_arr_phy);
+desc_err:
+	kfree(prtd);
+
+	return ret;
+}
+
+static int loongson_pcm_close(struct snd_soc_component *component,
+			      struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_card *card = substream->pcm->card;
+	struct loongson_runtime_data *prtd = substream->runtime->private_data;
+	struct loongson_dma_data *dma_data;
+
+	dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr,
+			  prtd->dma_desc_arr_phy);
+
+	dma_free_coherent(card->dev, sizeof(*prtd->dma_pos_desc),
+			  prtd->dma_pos_desc, prtd->dma_pos_desc_phy);
+
+	dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
+	free_irq(dma_data->irq, substream);
+
+	kfree(prtd);
+	return 0;
+}
+
+static int loongson_pcm_mmap(struct snd_soc_component *component,
+			     struct snd_pcm_substream *substream,
+			     struct vm_area_struct *vma)
+{
+	return remap_pfn_range(vma, vma->vm_start,
+			substream->dma_buffer.addr >> PAGE_SHIFT,
+			vma->vm_end - vma->vm_start, vma->vm_page_prot);
+}
+
+static int loongson_pcm_new(struct snd_soc_component *component,
+			    struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_pcm *pcm = rtd->pcm;
+
+	return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+					    pcm->card->dev,
+					    ls_pcm_hardware.buffer_bytes_max);
+}
+
+static const struct snd_soc_component_driver loongson_i2s_component = {
+	.name		= LS_I2S_DRVNAME,
+	.open		= loongson_pcm_open,
+	.close		= loongson_pcm_close,
+	.hw_params	= loongson_pcm_hw_params,
+	.trigger	= loongson_pcm_trigger,
+	.pointer	= loongson_pcm_pointer,
+	.mmap		= loongson_pcm_mmap,
+	.pcm_construct	= loongson_pcm_new,
+};
+
+static const struct regmap_config loongson_i2s_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.max_register = 0x110,
+	.cache_type = REGCACHE_FLAT,
+};
+
+static int loongson_i2s_pci_probe(struct pci_dev *pdev,
+				  const struct pci_device_id *pid)
+{
+	const struct fwnode_handle *fwnode = pdev->dev.fwnode;
+	struct loongson_dma_data *tx_data, *rx_data;
+	struct loongson_i2s *i2s;
+	int ret;
+
+	if (pci_enable_device(pdev)) {
+		dev_err(&pdev->dev, "pci_enable_device failed\n");
+		return -ENODEV;
+	}
+
+	ret = pci_request_region(pdev, BAR_NUM, LS_I2S_DRVNAME);
+	if (ret) {
+		dev_err(&pdev->dev, "request regions failed %d\n", ret);
+		goto err_disable;
+	}
+
+	i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
+	if (!i2s) {
+		ret = -ENOMEM;
+		goto err_release;
+	}
+	i2s->rev_id = pdev->revision;
+	i2s->dev = &pdev->dev;
+	pci_set_drvdata(pdev, i2s);
+
+	i2s->reg_base = pci_iomap(pdev, BAR_NUM, 0);
+	if (!i2s->reg_base) {
+		dev_err(&pdev->dev, "pci_iomap_error\n");
+		ret = -EIO;
+		goto err_release;
+	}
+
+	i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->reg_base,
+					    &loongson_i2s_regmap_config);
+	if (IS_ERR(i2s->regmap)) {
+		dev_err(&pdev->dev, "Failed to initialize register map");
+		ret = PTR_ERR(i2s->regmap);
+		goto err_unmap;
+	}
+
+	tx_data = &i2s->tx_dma_data;
+	rx_data = &i2s->rx_dma_data;
+
+	tx_data->dev_addr = (u64)i2s->reg_base + LS_I2S_TX_DATA;
+	tx_data->order_addr = i2s->reg_base + LS_I2S_TX_ORDER;
+
+	rx_data->dev_addr = (u64)i2s->reg_base + LS_I2S_RX_DATA;
+	rx_data->order_addr = i2s->reg_base + LS_I2S_RX_ORDER;
+
+	tx_data->irq = fwnode_irq_get_byname(fwnode, "tx");
+	if (tx_data->irq < 0) {
+		dev_err(&pdev->dev, "dma tx irq invalid\n");
+		ret = tx_data->irq;
+		goto err_unmap;
+	}
+
+	rx_data->irq = fwnode_irq_get_byname(fwnode, "rx");
+	if (rx_data->irq < 0) {
+		dev_err(&pdev->dev, "dma rx irq invalid\n");
+		ret = rx_data->irq;
+		goto err_unmap;
+	}
+
+	device_property_read_u32(&pdev->dev, "clock-frequency", &i2s->clk_rate);
+	if (!i2s->clk_rate) {
+		dev_err(&pdev->dev, "clock-frequency property invalid\n");
+		ret = -EINVAL;
+		goto err_unmap;
+	}
+
+	dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+
+	dev_set_name(&pdev->dev, "%s", loongson_i2s_dai.name);
+
+	loongson_i2s_init(i2s);
+
+	ret = devm_snd_soc_register_component(&pdev->dev,
+					      &loongson_i2s_component,
+					      &loongson_i2s_dai, 1);
+	if (ret) {
+		dev_err(&pdev->dev, "register DAI failed %d\n", ret);
+		goto err_unmap;
+	}
+
+	return 0;
+
+err_unmap:
+	pci_iounmap(pdev, i2s->reg_base);
+err_release:
+	pci_release_region(pdev, BAR_NUM);
+err_disable:
+	pci_disable_device(pdev);
+	return ret;
+}
+
+static void loongson_i2s_pci_remove(struct pci_dev *pdev)
+{
+	struct loongson_i2s *i2s = dev_get_drvdata(&pdev->dev);
+
+	pci_iounmap(pdev, i2s->reg_base);
+	pci_release_region(pdev, BAR_NUM);
+	pci_disable_device(pdev);
+}
+
+static const struct pci_device_id loongson_i2s_ids[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x7a27) },
+	{ },
+};
+MODULE_DEVICE_TABLE(pci, loongson_i2s_ids);
+
+static struct pci_driver loongson_i2s_driver = {
+	.name = "loongson-i2s-pci",
+	.id_table = loongson_i2s_ids,
+	.probe = loongson_i2s_pci_probe,
+	.remove = loongson_i2s_pci_remove,
+	.driver = {
+		.owner = THIS_MODULE,
+		.pm = pm_sleep_ptr(&loongson_i2s_pm),
+	},
+};
+module_pci_driver(loongson_i2s_driver);
+
+MODULE_DESCRIPTION("Loongson I2S Master Mode ASoC Driver");
+MODULE_AUTHOR("Loongson Technology Corporation Limited");
+MODULE_LICENSE("GPL");